关于Java多线程看这一篇就够了,从创建线程到线程池分析的明明白白
前言
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源,统一进程内的线程共享一个堆内存,每个线程具有自己的栈内存。“同时”执行是人的感觉,在线程之间实际上轮换执行。
同步与异步
同步:排队执行,效率低但是安全。
异步:同步执行,效率高但是数据不安全。
并发与并行
并发:指两个或多个事件在同一时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
线程创建的3种方式
1.继承方法
public class MyThread extends Thread{
@Override
public void run(){
}
}
MyThread m = new MyThread();
m.start();
2.接口方法
//实现runnable
//1 创建一个任务对象
MyRunnable r = new MyRunnable();
//创建一个线程并给他一个任务
Thread t = new Thread(r);
//启动线程
t.start();
接口的优势:
实现Runnable与继承Thread相比有如下优势
1.通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况
2,可以避免单继承所带来的局限性
3,任务与线程是分离的,提高了程序的健壮性
4,后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("12345"+i);
}
}
}.start();
但是继承Thread有个很简单的实现方式,通过匿名内部类重写run()不用重新创建一个类而简单的实现了多线程,每个线程都有自己的栈空间,而共用一个堆内存。
由一个线程调用的方法,方法指挥执行在此线程中。
3.Callable实现线程的状态的返回(实现了Callalble接口)
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执
行,如果不调用不会阻塞。
Callable<Integer> callable= new MyCallable();
FutureTask<Integer> future = new FutureTask<>(callable);
new Thread(future).start();
Integer j=task.get();
System.out.println("return"+j);
- 编写类实现Callable接口 , 实现call方法
class Mycallable implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
- 创建FutureTask对象 , 并传入第一步编写的Callable类对象 FutureTask future = new FutureTask<>(callable);
- 通过Thread,启动线程 new Thread(future).start();
如果调用get方法,则主线程等待其执行完成之后再执行,如果不调用则对主线程无影响可并行。
FutureTask()方法
其父类Future()方法
守护线程与用户线程
线程分为守护线程和用户线程
用户线程:当一个进程不包含任何的存活的用户线程时,进行结束
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
java默认线程为用户线程,通过线程调用setDaemon(true)设置守护线程。
Thread t1 = new Thread(new MyRunnable());
//设置守护线程
t1.setDaemon(true);
t1.start();
线程同步的三种方法
1.同步代码块,指定锁比如同一个object
格式:synchronized(锁对象){}
public class Demo8 {
public static void main(String[] args) {
Object o = new Object();
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//总票数
private int count = 10;
private Object o = new Object();
@Override
public void run() {
while (true) {
synchronized (o) {
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
}else {
break;
}
}
}
}
}
}
2. 同步方法
run()里面调用同步方法,如果是普通方法使用方法内部的this(此对象)作为锁,如果是静态方法是类名.class(字节码文件对象)
public synchronized boolean test(){
System.out.println("测试");
}
3.显示锁
同步代码块和同步方法都是隐式锁。
显式锁更加直观,体现面向对象,自己生成锁,自己加锁解锁。
static class Test implements Runnable{
//总票数
//参数为true表示公平锁 默认是false 不是公平锁
private Lock l = new ReentrantLock(true);
@Override
public void run() {
l.lock();
System.out.println("测试");
l.unlock();
}
}
公平锁与不公平锁
公平锁:先来先到,一起排队
不公平锁:大家一起抢
java默认都是不公平锁,可以通过显式锁中的构造方法实现公平锁。
//设置公平锁
private Lock l= new ReentrantLock(true);
不传参数默认false不公平,传入参数则公平
死锁的避免
在任何有可能导致锁产生的方法里,不要调用另外一个有可能产生锁的方法让另外一个锁产生。
下面是死锁产生的例子:
public class Demo11 {
public static void main(String[] args) {
//线程死锁
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
MyThread(Culprit c,Police p){
this.c = c;
this.p = p;
}
@Override
public void run() {
p.say(c);
}
}
static class Culprit{
public synchronized void say(Police p){//两方法公用this(此对象)锁,第一个方法不执行完无法执行第二个方法
System.out.println("罪犯:你放了我,我放了人质");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯被放了,罪犯也放了人质");
}
}
static class Police{
public synchronized void say(Culprit c){
System.out.println("警察:你放了人质,我放了你");
c.fun();
}
public synchronized void fun(){
System.out.println("警察救了人质,但是罪犯跑了");
}
}
}
此时为死锁
若第一个线程执行(假设)p.fun()时,第二个线程p还没执行p.say()方法,此时没锁到,两方法都能执行,但出现错误。
生产者与消费者
1.一个类,可以修改可以读取,如果不使用线程同步,在修改时发生了另一线程的读取操作,会发生读取脏数据的情况。
2.通过加入synchronized线程同步方法,修改时无法读取可以解决读取脏数据的问题,但是无法实现两线程之间的同步,会发生一个线程的抢占时间片现象无法同步。
3.如果想要读取和修改可以互相等待同步,还需要加入wait和notify操作,一个执行完notifyall休息,置判别标识,另一个被唤醒后读取判别标识执行然后休息,在唤醒另一个。加入flag标志判断线程执行先后。
package com.java.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo4 {
/**
* 多线程通信问题, 生产者与消费者问题
* @param args
*/
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag) {
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程的状态
线程状态。 线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。
RUNNABLE
在Java虚拟机中执行的线程处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
TERMINATED
已退出的线程处于此状态。
线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。
线程池
1.缓存线程池
无限制长度
任务加入后的执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在则创建线程并使用
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"测试");
}
});
2.定长线程池
长度是指定的线程池
加入任务后的执行流程:
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
//设置定长线程池
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"测试");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
3.单线程线程池
效果与长度为1的定长线程池一样。
执行流程
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"测试");
}
});
4.周期定长线程池
执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
周期性任务执行时:
定时执行 当某个任务触发时 自动执行某任务
执行一次:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//定时执行一次
//参数1:定时执行的任务
//参数2:时长数字
//参数3:2的时间单位 Timeunit的常量指定
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"测试");
}
},5, TimeUnit.SECONDS); //5秒钟后执行*/
周期执行
周期性执行任务
参数1:任务
参数2:延迟时长数字(第一次在执行上面时间以后)
参数3:周期时长数字(没隔多久执行一次)
参数4:时长数字的单位
* **/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"测试");
}
},5,1,TimeUnit.SECONDS);
Lambda表达式
再使用匿名内部类作为参数时,可以使用lambda写法来极大的简化代码。
//冗余的Runnable编写方式
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("测试");
}
});
t.start();
保留传递的参数,保留要重写的方法体,中间用-> 连接
Thread t = new Thread(() -> System.out.println("测试"));
最后
感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!
关于Java多线程看这一篇就够了,从创建线程到线程池分析的明明白白的更多相关文章
- Java多线程看这一篇就足够了(吐血超详细总结)
进程与线程 进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程.多进程操作系统能同时达运行多个进程(程序),由于 ...
- Java 集合看这一篇就够了
大家好,这里是<齐姐聊数据结构>系列之大集合. 话不多说,直接上图: Java 集合,也称作容器,主要是由两大接口 (Interface) 派生出来的: Collection 和 Map ...
- Java NIO看这一篇就够了
原文链接:https://mp.weixin.qq.com/s/c9tkrokcDQR375kiwCeV9w? 现在使用NIO的场景越来越多,很多网上的技术框架或多或少的使用NIO技术,譬如Tomca ...
- 想真正了解JAVA设计模式看着一篇就够了。 详解+代码实例
Java 设计模式 设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结 设计模式分为 23 种经典的模式,根据用途我们又可以分为三大类.分别是创建型模式.结构型模式和行为型模式 列举几种设 ...
- Java注解 看这一篇就够了
注解 1.概念 注解:说明程序的.给计算机看的 注释:用文字描述程序的.给程序员看的 注解的定义:注解(Annotation),也叫元数据.一种代码级别的说明.它是JDK1.5及以后版本引入的一个特性 ...
- Java中的多线程=你只要看这一篇就够了
如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...
- 【Todo】【读书笔记】Java多线程编程指南-设计模式篇
下了这本书<Java多线程编程指南-设计模式篇>, 还有另一本<JAVA多线程设计模式>,据说内容有重复,结合着看.
- 【java编程】ServiceLoader使用看这一篇就够了
转载:https://www.jianshu.com/p/7601ba434ff4 想必大家多多少少听过spi,具体的解释我就不多说了.但是它具体是怎么实现的呢?它的原理是什么呢?下面我就围绕这两个问 ...
- JVM内存模型你只要看这一篇就够了
JVM内存模型你只要看这一篇就够了 我是一只孤傲的鱼鹰 让我们不厌其烦的从内存模型开始说起:作为一般人需要了解到的,JVM的内存区域可以被分为:线程栈,堆,静态方法区(实际上还有更多功能的区域,并且这 ...
随机推荐
- Vue中监听 键盘事件及修饰符
键盘事件: keyCode 实际值 48到57 0 - 9 65到90 a - z ( A-Z ) 112到135 F1 - F24 8 ...
- centos mysql5.7安装
1. 安装 1 wget http://repo.mysql.com//mysql57-community-release-el7-11.noarch.rpm 2 rpm -ivh mysql57-c ...
- 在centos下启动nginx出现Failed to start nginx.service:unit not found
错误的原因就是没有添加nginx服务,所以启动失败. 解决方法: 1. 在/root/etc/init.d/目录下新建文件,文件名为nginx 或者用命令在根目录下执行:# vim /etc/i ...
- 在电脑上操作手机屏幕scrcpy工具就搞定了
手机安卓版本:10 电脑:win64 使用步骤 电脑步骤 下载scrcpy scrcpy 是免费开源的投屏软件,支持将安卓手机屏幕投放在 Windows.macOS.GNU/Linux 上,并可直接借 ...
- 1024|推荐一个开源免费的Spring Boot教程
2020-1024=996! 今天,星期六,你们是否加班了?我反正加了!早上去公司开了一早上会,中午回家写下了这篇文章. 今天,我要推荐一个开源免费的Spring Boot项目,就是我最近日更的Spr ...
- Tomcat8升级后URL中特殊字符报错出现原因
请求带上花括号等字符,请求无法送达服务端,报错: Failed to load resource: the server responded with a status of 400 () https ...
- [阿里DIN] 从论文源码学习 之 embedding层如何自动更新
[阿里DIN] 从论文源码学习 之 embedding层如何自动更新 目录 [阿里DIN] 从论文源码学习 之 embedding层如何自动更新 0x00 摘要 0x01 DIN源码 1.1 问题 1 ...
- js 重排和重绘
1.什么是重排和重绘 浏览器下载完页面中的所有组件--HTML标记.JavaScript.CSS.图片之后会解析生成两个内部数据结构--DOM树和渲染树. DOM树表示页面结构,渲染树表示DOM节点如 ...
- 【Aspose.Words for Java】 对word文档,增加页眉,页脚,插入内容区图像,
一.环境准备 jar包:aspose-words-20.4.jar 或者去官方网站下载: 官方网站:https://www.aspose.com/ 下载地址:https://downloads.asp ...
- 删除指定路径下指定天数之前(以文件的最后修改日期为准)的文件:BAT + VBS
代码如下: @echo off ::演示:删除指定路径下指定天数之前(以文件的最后修改日期为准)的文件. ::如果演示结果无误,把del前面的echo去掉,即可实现真正删除. ::本例调用了临时VBS ...