线程
守护线程
eg:GC垃圾回收
注意
在jvm中,如果用户线程正在执行任务,则进程不会关闭。
如果用户线程全部执行完毕,即使GC垃圾回收正在执行,jvm也会关闭进程。
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
System.out.println("t1 run " + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//将该线程设置为守护线程 (需在start之前设置该属性)
//t1.setDaemon(true);
t1.start();
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
System.out.println("t2 run " + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
}
线程优先级
线程的切换是通过线程调度器 分配时间片来实现控制的。在代码层面上,我们可以通过提高线程的优先级来最大程度改善线程获取时间片的几率。(一个cpu内核同一时间只能执行一个线程,想要实现同一时间执行多线程,实际上是通过线程调度器分配时间片实现的)。
线程的优先级共10级,1-10。1最低,10最高。不设置时默认为5.
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
System.out.println("t1 run " + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//设置优先级 1最低 10最高 如果不设置,默认为5
t1.setPriority(1);
t1.start();
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
System.out.println("t2 run " + i);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.setPriority(10);
t2.start();
}
线程停止
过去:
thread.stop()
已废弃:内部通过抛出异常的方式停止线程,过于暴力。
目前:
t2.isInterrupted(); 线程是否被中断
t2.interrupt(); 中断线程
线程的生命周期
新建NEW:new
就绪 :.start()
运行RUNNABLE:获取到时间片
阻塞
BLOCKED : 抢锁失败,等待 锁释放-进入就绪状态继续抢锁
WAITING : wait() 方法 notify()方法唤醒
TIMED_WAITING : sleep(time)方法 time时间后自动醒来
死亡TERMINATED
join
Thread t1 = new Thread(() -> {
//--------
});
Thread t2 = new Thread(() -> {
t1.join();
//---------------
});
t2.start();
t1.start();
B线程中调用了A线程的join方法,那么会先将A线程执行完成后再执行B线程。
并发问题原因
主线程存在一个共享变量number = 100;
A线程
1.读取number = 100
2.将其加载到自己的工作内存
3.将number - 1 ,修改值为99
4.将99修改到主线程
B线程在A线程的4操作之前开始读取,此时读取到的值是100,是还未更改过的值,因此当B全流程操作完之后也会将主线程的值更改为99,但实际上我们希望的值是98
java并发编程的三大特性
1.原子性
多个线程同时操作一个变量时,对于一个操作或者多个操作,要么全部执行并且在执行过程中不被打断,要么全部不执行。
通过加锁实现
2.可见性
当多个线程同时访问同一个变量的时候,其中一个线程修改了变量的值,其他线程能够立刻看得到修改的值。
public class test {
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
//线程1
new Thread(()->{
System.out.println("start");
int number = 0;
while (!flag) {
number++;
}
System.out.println("end");
}).start();
Thread.sleep(1000);
//线程2
new Thread(() -> {
System.out.println("change flag to true");
flag = true;
}).start();
}
}
执行结果为:
线程2修改flag为true后,线程1并没有结束while循环。这是因为线程1读到的flag值为false,线程2修改了值之后,该值最后同步修改到了主线程的共享变量中,但是在线程1中的值仍旧是没有修改之前的值false。线程之间的本地副本变量是不共享的。
3.有序性
程序执行的顺序按照代码的先后顺序执行
一般来说处理器为了提高程序执行效率,可能会对输入代码进行优化,他不保证程序中各个语句执行的先后顺序和代码中的顺序一样,但是他会保证程序最终执行结果和代码顺序执行的结果一致。
int a = 10;1
a = a + 10;2
int b = 5;3
b = a * a;4
处理器会在不影响执行结果的情况下,优化代码的执行顺序。单线程情况下没有任何影响,但是多线程情况下变会出现问题。
volatile
优点:可见性、有序性
缺点:无法保证原子性
synchronized
保证同一时刻只有一个线程可以执行某个方法或者代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile
多线程之间的通信
多个线程在处理同一个资源,并且任务不同的时候,需要线程通信来帮助解决线程之间对同一个变量的使用或者操作。
于是引出了等待唤醒机制:notify(),wait().
wait(),notify(),notifyAll()这三个方法是定义在object类中的方法,可以用来控制线程的状态。
这三个方法最终调用的都是jvm级的native方法,随着jvm运行平台的不同可能有些许差异。
wait()方法的调用必须放在synchronized方法或者synchronized块中。
原因:wait()方法会释放锁,进入等待状态,因此她必须在synchronized方法中,表示当前线程是有锁的。
另外:调用wait方法的对象必须是当前被加了锁的对象。wait方法会抛出异常,因此需要捕获或者抛出异常。
线程池
体系结构
位于java.util.concurrent包下
public interface Executor {//该接口是最顶级的接口
//线程执行任务的方法
void execute(Runnable command);
}
java.util.concurrent.Executor 负责线程的使用和调度的根接口,提供了执行任务的方法execute().
|--ExecutorService 子接口: 线程池的主要接口
|--ThreadPoolExecutor 线程池的实现类
|--ScheduledExceutorService 子接口: 负责线程的调度
|--ScheduledThreadPoolExecutor : 继承ThreadPoolExecutor,实现了ScheduledExecutorService
关键类或接口 | 含义 |
---|---|
Executor | 是一个接口,它是Executor框架的基础, 它将任务的提交与任务的执行分离开来 |
ExecutorService | 线程池的主要接口,是Executor的子接口 |
ThreadPoolExecutor | 是线程池的核心实现类,用来执行被提交的任务 |
ScheduledThreadPoolExecutor | 另一个关键实现类,可以进行延迟或者定期执行任务。ScheduledThreadPoolExecutor比Timer定时器更灵活, 功能更强大 |
Future接口与FutureTask实现类 | 代表异步计算的结果 |
Runnable接口和Callable接口的实现类 | 都可以被ThreadPoolExecutor或 ScheduledThreadPoolExecutor执行的任务 |
Executors | 线程池的工具类,可以快捷的创建线程池 |
组件 | 含义 |
---|---|
int corePoolSize | 核心线程池的大小(线程池创建几个固定的线程来执行任务) |
int maximumPoolSize | 最大线程池的大小(当任务队列满了的时候,会创建max-core个临时线程来帮助程序处理任务) |
BlockingQueue workQueue | 用来暂时保存任务的工作队列 |
RejectedExecutionHandler | 当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和时(达到了最大线程池的大小且工作队列已满),execute()方法将要调用的Handler |
long keepAliveTime, | 表示空闲线程的存活时间。(核心线程默认没有超时时间,一直存在) |
TimeUnit | 表示keepAliveTime的单位。 |
ThreadFactory threadFactory | 指定创建线程的线程工厂 |
/**
* int corePoolSize, 核心线程数
* int maximumPoolSize,最大线程数
* long keepAliveTime,存活时长
* TimeUnit unit, 时间单位
* BlockingQueue<Runnable> workQueue 工作队列
* RejectedExecutionHandler handler 饱和策略 如果队列满了,最大线程数的所有线程也都在执行任务,此时还有任务要进入队列去想要被执行,饱和策略就会抛出异常,丢弃该新的任务
*/
ExecutorService executorService = new ThreadPoolExecutor(
2,
5,
10,
TimeUnit.MILLISECONDS, //maximumPoolSize 的线程在该时间内没有新任务,线程就会被释放
new ArrayBlockingQueue<Runnable>(10),//阻塞队列 当插入不进去的时候,会被阻塞。当取数据取不出来的时候,会被阻塞
new ThreadPoolExecutor.DiscardOldestPolicy());
线程池的处理流程
提交一个任务到线程池中,线程池的处理流程如下:
-
流程1 判断核心线程数
判断正在运行的工作线程是否小于 设置的核心线程数,小于尝试创建一个新的工作线程,如果不小于进入下一流程 -
流程2 判断任务能否加入到任务队列
判断当前线程池的任务队列是否已满,未满的话将任务加入任务队列,如果满了,进入下一个流程 -
流程3 判断最大线程数
判断当前线程池的工作线程是否小于 设置的最大线程数,小于尝试创建一个新的临时工作线程,如果不小于进入下一流程 -
流程4 根据线程池的饱和策略处理任务
到此流程,说明当前线程池已经饱和,需要进行拒绝策略,根据设置的拒绝策略进行处理
这里我们可以理解线程池为一家公司,刚创立起来时,公司的状态是RUNNING;员工便是线程,招聘员工成功后,员工数增加 = 线程数增加。
其中
核心线程(corePoolSize) = ‘正式员工’
max-core的线程 = ‘外包员工’
存活时长(keepAliveTime) = ‘工作时长’
‘正式员工’不会被开除,因此‘工作时长’的设置对‘正式员工’无效
‘工作时长’只针对‘外包员工’
且公司福利优先考虑‘正式员工’,同理任务优先考虑‘正式员工’去执行(因为有奖金)
所以当‘正式员工’无空闲时,任务会先放到队列中等待
队列也满了&仍旧无‘正式员工’空闲的情况下,才招聘‘外包员工’执行任务
‘外包员工’任务执行完成后空闲了‘工作时长’,便被开除.
‘外包员工’也无空闲的情况下,‘公司’只能采取饱和策略针对性的处理任务。
线程池的关闭方法
//等待任务队列所有的任务执行完成之后才关闭
executorService.shutdown();
//立即关闭线程池,并返回剩余的任务
executorService.shutdownNow();
线程池的状态
线程池的类型
public static void main(String[] args) {
//可调度的线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
/**
* 延迟周期性执行
* 参数 线程 延迟时间 间隔时间 时间单位
*/
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("run");
}
},2,2, TimeUnit.SECONDS);
/**
*
*/
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("run");
}
},2,TimeUnit.SECONDS);
//可缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//定长线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
//单线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
}
但是阿里巴巴不建议使用这四种线程池,建议自己写线程池去执行业务,如下,因为自己可以控制里面的参数情况。
ExecutorService executorService = new ThreadPoolExecutor(2,5,10,
TimeUnit.MILLISECONDS, //maximumPoolSize 的线程在该时间内没有新任务,线程就会被释放
new ArrayBlockingQueue<Runnable>(10),//阻塞队列 当插入不进去的时候,会被阻塞。当取数据取不出来的时候,会被阻塞
new ThreadPoolExecutor.DiscardOldestPolicy());
使用多线程要注意的问题:
1.线程安全问题
2.线程性能问题(创建、维护线程都需要消耗系统资源)(线程的上下文切换也需要消耗时间),因此线程并不是创建越多越好
根据自己电脑的配置和具体要执行的业务决定线程创建数。
线程池的作用
1.通过创建固定数量的线程来执行大量的任务
2.可以很好的复用线程,减少线程的创建和维护的时间消耗
源码
我们使用线程池大多是在高并发情况下。因此线程池必须保证其内部是线程安全的。
这里使用了一个**Integer的变量(AtomicInteger)**去维护线程池的生命状态和当前工作线程数量。
这里我们先了解一下AtomicInteger
AtomicInteger
作用
维护用于存储线程池状态和线程数量的int变量
AtomicInteger类源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
AtomicInteger类的value值,二进制前三位表示线程池的状态,后29位表示线程池的线程数量。
线程池的基本参数
可以看到源码中维护了线程池的五种状态
CAPACITY表示线程池中的最大工作线程数量上限
//在ctlOf()方法中,传入的参数是-1 和0,返回的结果是-1
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer.SIZE是32,减去3则为29.即低292位表示当前工作线程的数量 高3位表示当前线程池的生命周期
private static final int COUNT_BITS = Integer.SIZE - 3;
//1 << COUNT_BITS 为001, 这个常量表示workerCount的上限值,大约是5亿
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//111
private static final int RUNNING = -1 << COUNT_BITS;
//000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//001
private static final int STOP = 1 << COUNT_BITS;
//010
private static final int TIDYING = 2 << COUNT_BITS;
//011
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
推导过程
1. 了解二进制和十进制
十进制 | 二进制 |
---|---|
-1 | 1111 1111 1111 1111 1111 1111 1111 1111 |
0 | 0000 0000 0000 0000 0000 0000 0000 0000 |
1 | 0000 0000 0000 0000 0000 0000 0000 0001 |
2 | 0000 0000 0000 0000 0000 0000 0000 0010 |
3 | 0000 0000 0000 0000 0000 0000 0000 0011 |
RUNNING(-1 左位移29) | 1110 0000 0000 0000 0000 0000 0000 0000 |
SHUTDOWN(0 左位移29) | 0000 0000 0000 0000 0000 0000 0000 0000 |
STOP(1 左位移29) | 0010 0000 0000 0000 0000 0000 0000 0000 |
TIDYING(2 左位移29) | 0100 0000 0000 0000 0000 0000 0000 0000 |
TERMINATED(3 左位移29) | 0110 0000 0000 0000 0000 0000 0000 0000 |
CAPACITY | 0001 1111 1111 1111 1111 1111 1111 1111 |
~CAPACITY | 1110 0000 0000 0000 0000 0000 0000 0000 |
2. COUNT_BITS的计算
private static final int COUNT_BITS = Integer.SIZE - 3;
Integer.SIZE是32,减去3则为29.
2. CAPACITY的计算
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
即
- 1 的二进制: 0000 0000 0000 0000 0000 0000 0000 0001
- 1 << COUNT_BITS 表示1的二进制向左位移29位
1 << COUNT_BITS = 0010 0000 0000 0000 0000 0000 0000 0000 - 再减去1
此处我们将1转换为二进制进行减法操作
1的二进制为0000 0000 0000 0000 0000 0000 0000 0001
所以是
0010 0000 0000 0000 0000 0000 0000 0000 - 0000 0000 0000 0000 0000 0000 0000 0001
相减的过程中,从末位开始相减,如果数值不够,就向前一位借1,本位加2.
所以二者相减的结果为
0001 1111 1111 1111 1111 1111 1111 1111
即CAPACITY = 0001 1111 1111 1111 1111 1111 1111 1111
3. 对clt进行计算的方法
//获取运行状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取活动线程数
private static int workerCountOf(int c) { return c & CAPACITY; }
//获取运行状态和活动线程数的值
private static int ctlOf(int rs, int wc) { return rs | wc; }
runStateOf(获取线程池状态)
private static int runStateOf(int c) {
return c & ~CAPACITY;
}
推导过程:
-
CAPACITY = 0001 1111 1111 1111 1111 1111 1111 1111
~CAPACITY = 1110 0000 0000 0000 0000 0000 0000 0000
-
如果参数c是RUNNING
RUNNING 的二进制为1110 0000 0000 0000 0000 0000 0000 0000 -
程序源码中获取线程池的状态的代码是这样的:
c = ctl.get(); 将c作为参数传入runStateOf()方法中 -
在runStateOf©方法中,具体执行 c & ~CAPACITY
-
&运算表示 如果都为1,则为1,如果有0,则为0
-
~CAPACITY的前三位为111
因此无论c是多少,运算的结果的二进制前三位一定是c的二进制的前三位 -
方法的结果二进制为1110 0000 0000 0000 0000 0000 0000 0000
workerCountOf(获取线程池线程数量)
private static int workerCountOf(int c) {
return c & CAPACITY;
}
推导过程:
- CAPACITY = 0001 1111 1111 1111 1111 1111 1111 1111
- 如果c是RUNNING,其二进制为1110 0000 0000 0000 0000 0000 0000 0001
- c & CAPACITY进行&运算的时候,二进制后29位表示线程池中的线程数量
- 此时的结果为0000 0000 0000 0000 0000 0000 0000 0001,即转换为十进制为1,即当前线程池中的线程数量为1.
ctlOf
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
1.ctlof()方法中对RUNNING和0进行|操作,结果为:1110 0000 0000 0000 0000 0000 0000 0000,即RUNNING的十进制
2.new AtomicInteger()将RUNNING的十进制赋值到构造方法
3.AtomicInteger内部维护着volatile int value,因此初始化线程池的时候,该value值为RUNNING的十进制,表示线程池为运行状态,且池内线程数量为0.
推导RUNNING的计算方法
首先了解一个知识
-1 用二进制表示为:1111 1111 1111 1111 1111 1111 1111 1111
原因:
正负号用第一位表示:0表示正数 1表示负数
1=0000 0000 0000 0000 0000 0000 0000 0001
当转换为-1的时候,最高位用1表示,其余位均要取反,因此是
-1=1111 1111 1111 1111 1111 1111 1111 1110,最后一位要进行补码+1,因此
-1=1111 1111 1111 1111 1111 1111 1111 1111
private static final int RUNNING = -1 << COUNT_BITS;
即-1向左位移29位,右侧空余补0,因此
RUNNING= 1110 0000 0000 0000 0000 0000 0000 0000,
即高3位 111 =RUNNING
execute()
//提交任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//clt记录着runState和workerCount
int c = ctl.get();
/*
* workerCountOf方法取出低29位的值,表示当前活动的线程数;
* 如果当前活动线程数小于corePoolSize,则新建一个线程放入线程池中;
* 并把任务添加到该线程中。
* 新建线程的方法就是addWorker()方法
*/
if (workerCountOf(c) < corePoolSize) {
/*
* addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize来判断还是maximumPoolSize来判断;
* 如果为true,根据corePoolSize来判断;
* 如果为false,则根据maximumPoolSize来判断
*/
if (addWorker(command, true))
return;
//如果添加线程失败,则重新获取clt
c = ctl.get();
}
//如果当前线程池是运行状态并且任务也添加到队列添加成功
if (isRunning(c) && workQueue.offer(command)) {
//重新获取clt
int recheck = ctl.get();
//如果当前线程池不是运行状态,就需要将刚刚添加到队列中的任务移除
//如果任务也移除成功了 此时就要执行饱和策略拒绝任务
if (! isRunning(recheck) && remove(command))
reject(command);
/*
* 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
* 这里传入的参数表示:
* 1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
* 2. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断;
* 如果判断workerCount大于0,则直接返回,在workQueue中新增的command会在将来的某个时刻被执行。
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 如果执行到这里,有两种情况:
* 1. 线程池已经不是RUNNING状态;
* 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
* 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;
* 如果失败则拒绝该任务
*/
else if (!addWorker(command, false))
reject(command);
}
简单来说,在执行execute()方法时如果状态一直是RUNNING时,的执行过程如下:
如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;
如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;
如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;
如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
这里要注意一下addWorker(null, false);,也就是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,所以worker在执行的时候,会直接从workQueue中获取任务。所以,在workerCountOf(recheck) == 0时执行addWorker(null, false);也是为了保证线程池在RUNNING状态下必须要有一个线程来执行任务。
execute()流程图
addWorker
/**
* core true表示添加的是核心线程 false 表示添加的是临时线程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
//获取线程池的运行状态
int rs = runStateOf(c);
/*
* 这个if判断
* 如果rs >= SHUTDOWN,则表示此时不再接收新任务;
* 接着判断以下3个条件,只要有1个不满足,则返回false:
* 1. rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
* 2. firsTask为空
* 3. 阻塞队列不为空
*
* 首先考虑rs == SHUTDOWN的情况
* 这种情况下不会接受新提交的任务,所以在firstTask不为空的时候会返回false;
* 然后,如果firstTask为空,并且workQueue也为空,则返回false,
* 因为队列中已经没有任务了,不需要再添加线程了
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// 如果wc超过CAPACITY,也就是ctl的低29位的最大值(二进制是29个1),返回false;
// 这里的core是addWorker方法的第二个参数,如果为true表示根据corePoolSize来比较,
// 如果为false则根据maximumPoolSize来比较。
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 尝试增加workerCount,如果成功,则跳出第一个for循环
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行
if (runStateOf(c) != rs)
continue retry;
}
}
//上面的for循环只是为了判断状态,状态无误的情况下便开始创建线程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 根据firstTask来创建Worker对象
w = new Worker(firstTask);
// 每一个Worker对象都会创建一个线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// rs < SHUTDOWN表示是RUNNING状态;
// 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。
// 因为在SHUTDOWN时不会在添加新的任务,但还是会执行workQueue中的任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
getTask
private Runnable getTask() {
// timeOut变量的值表示上次从阻塞队列中取任务时是否超时
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/*
* 如果线程池状态rs >= SHUTDOWN,也就是非RUNNING状态,再进行以下判断:
* 1. rs >= STOP,线程池是否正在stop;
* 2. 阻塞队列是否为空。
* 如果以上条件满足,则将workerCount减1并返回null。
* 因为如果当前线程池状态的值是SHUTDOWN或以上时,不允许再向阻塞队列中添加任务。
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// timed变量用于判断是否需要进行超时控制。
// allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时;
// wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
// 对于超过核心线程数量的这些线程,需要进行超时控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* wc > maximumPoolSize的情况是因为可能在此方法执行阶段同时执行了setMaximumPoolSize方法;
* timed && timedOut 如果为true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时
* 接下来判断,如果有效线程数量大于1,或者阻塞队列是空的,那么尝试将workerCount减1;
* 如果减1失败,则返回重试。
* 如果wc == 1时,也就说明当前线程是线程池中唯一的一个线程了。
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/*
* 根据timed来判断,如果为true,则通过阻塞队列的poll方法进行超时控制,如果在keepAliveTime时间内没有获取到任务,则返回null;
* 否则通过take方法,如果这时队列为空,则take方法会阻塞直到队列不为空。
*
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 如果 r == null,说明已经超时,timedOut设置为true
timedOut = true;
} catch (InterruptedException retry) {
// 如果获取任务时当前线程发生了中断,则设置timedOut为false并返回循环重试
timedOut = false;
}
}
}
runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
/**
*如果线程池正在停止,那么要保证当前线程是中断状态;
如果不是的话,则要保证当前线程不是中断状态;
*/
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//beforeExecute方法和afterExecute方法在ThreadPoolExecutor类中是空的,留给子类来实现。
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//beforeExecute方法和afterExecute方法在ThreadPoolExecutor类中是空的,留给子类来实现。
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
1.while循环不断地通过getTask()方法获取任务;
2.getTask()方法从阻塞队列中取任务;
3.如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态;
4.调用task.run()执行任务;
5.如果task为null则跳出循环,执行processWorkerExit()方法;
6.runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。
线程池execute流程图
队列
FIFO
1.在任意时刻,不管并发有多高,永远只有一个线程能够进行队列的入队或者出队操作。这意味着这是一个线程安全的队列。
2.队列满,只能进行出队操作中,所有入队的操作必须等待,也就是被阻塞。
3.队列空,只能进行入队操作,所有出队的操作必须等待,也就是被阻塞。
1.SynchronousQueue
该队列没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。
使用该阻塞队列一般要求maximumPoolSize为无界,避免线程拒绝执行操作。
public static void main(String[] args) {
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
//生产者线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
System.out.println("装入数据:" + i);
queue.put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//消费者线程
Thread t2 = new Thread(() -> {
while (true) {
try {
System.out.println("取出数据:" + queue.take());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
2.LinkedBlockingQueue
该队列是一个有界缓存等待队列,可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的元素缓存在该队列中等待有空闲的线程时继续执行,当该队列已满的时候,新的任务加入该队列会失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSize时,再有新的元素尝试加入队列便会报错。
3.ArrayBlockingQueue
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(4);
参数为队列的大小
该队列是一个有界缓存等待队列,可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize的时候,多余的元素缓存在队列中等待有空闲的线程时继续执行,当队列已满时,加入该队列会失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSize时,再有新的元素尝试加入队列时会报错。
饱和策略
一共有四种饱和策略,还有四种扩展方案。所以网上有些回答说是共有八种饱和策略。
1.AbortPolicy(默认的策略)
抛出异常,丢弃任务
2.CallerRunsPolicy
不抛弃任务,调用线程池的那个线程去帮忙执行任务
3.DiscardPolicy
直接丢弃任务,丢弃的任务是最新来的任务,并且没有任务提示也没有任何日志打印。
4.DiscardOldestPolicy
丢弃的任务是在队列中还没有执行的最早的任务。
位运算符
&与 都为1则返回1,不是都为1则返回0
|或 只要有一个为1则返回1,没有1则返回0
~取反 0反为1,1反为0
^按位异或 跟 | 类似,但有一点不同的是 如果两个操作位都为1的话,结果产生0
>>右移 2>>1,则表示2的二进制右移动一位,空出的位则补0(如果是负数右移。则空出的位补1。因为最高位为1表示当前数为负数);也表示2除以2的1次方
<<左移 8<<1,则表示8的二进制左移动一位,空出的位则补0;也表示8乘以2的1次方
任何小数 把它 >> 0可以取整
如3.14159 >> 0 = 3;
无符号右移位(>>>) a >>> 2 被移位二进制最高位无论是0还是1,空缺位都用0补
参考
- https://blog.csdn.net/S11035762/article/details/110954901?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaidudefault-1.highlightwordscore&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaidudefault-1.highlightwordscore