线程回顾
创建一个线程需要做如下两件事:
- 继承Thread
- 实现Runnable
java
public void Test(){
Thread thread = new Thread(new Myrunnable());
thread.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
static class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
线程的状态
-
新建(NEW)
- 线程已经被创建,但还没有开始执行(也就是说,
Thread.start()
方法还没有被调用)。
- 线程已经被创建,但还没有开始执行(也就是说,
-
可运行(RUNNABLE)
- 处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。
-
就绪(READY)
- 线程已经调用了
start()
方法,但尚未被线程调度器选中去执行。它们准备好运行,并等待CPU。
- 线程已经调用了
-
运行(RUNNING)
- 线程当前正在执行。如果线程的
run()
方法正在执行,那么它就处于这个状态。
- 线程当前正在执行。如果线程的
-
阻塞(BLOCKED)
- 线程在等待一个监视器锁(进入一个
synchronized
块或方法),以便进入一个同步的区域。
- 线程在等待一个监视器锁(进入一个
-
等待(WAITING)
- 线程通过调用
Object.wait()
、Thread.join()
或LockSupport.park()
进入无限期等待状态,等待另一个线程的通知或中断。
- 线程通过调用
-
定时等待(TIMED_WAITING)
- 线程在一定的时间内等待另一个线程的动作。与WAITING不同,它会在调用如
Thread.sleep(long)
、Object.wait(long)
、Thread.join(long)
或LockSupport.parkNanos()
/LockSupport.parkUntil()
时发生,这些调用都带有时间限制。
- 线程在一定的时间内等待另一个线程的动作。与WAITING不同,它会在调用如
-
终止(TERMINATED)
- 线程的运行已经结束,因为
run()
方法执行完毕或者因为异常退出。
- 线程的运行已经结束,因为
图中还展示了线程如何从一个状态转换到另外一种状态。
eg:一个RUNNING状态的线程可能通过调用 yield()
方法回到READY状态,或者因为调用 sleep()
、wait()
等方法进入WAITING或TIMED_WAITING状态。等待的线程可能会在收到 notify()
、notifyAll()
或LockSupport.park();
时返回到READY状态。一个处于BLOCKED状态的线程,在获取到锁之后会进入RUNNING状态。
但是一般都不会直接去继承Thread或者实现Runnable,因为这样会有一些弊端:
- 线程资源无法重复利用,开销较大
- 创建线程等待时间较长
- 线程无限创建可能导致内存占用过大OOM(没内存了)
线程池
根据上面的状态,普通线程执行完,就会进入TERMINATED销毁掉,而线程池就是创建一个缓冲池存放线程,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等候下次任务来临,这使得线程池比手动创建线程有着更多的优势:
-
降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
-
提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
-
方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM
-
节省cpu切换线程的时间成本(需要保持当前执行线程的现场,并恢复要执行线程的现场)。
- 保存线程现场:操作系统保存当前线程的状态信息,包括程序计数器、寄存器内容和内存状态等。
- 恢复新线程现场:操作系统将另一个线程之前保存的状态信息恢复,以便这个线程可以继续执行。
线程上下文切换是有成本的,因为它需要CPU周期来保存和加载线程的状态信息,并且在切换过程中CPU不做任何实际的工作,只是在两个线程之间传递。当频繁进行上下文切换时,这个开销就变得非常可观。
线程池可以减少上下文切换的频率和成本:
- 固定数量的线程:线程池中通常有一个固定数量的工作线程。这意味着不需要为每个新任务频繁地创建和销毁线程,从而避免了不断进行上下文切换。
- 任务队列:线程池通常维护一个待执行任务的队列。工作线程在执行完一个任务后,可以立即从队列中取出下一个任务并开始执行,而不是结束并等待系统调度新的线程。
- 减少延迟:由于线程已经创建好并且就绪,线程池还可以减少任务的启动延迟,因为无需等待新线程的创建。
-
提供更强大的功能,延时定时线程池。(Timer vs ScheduledThreadPoolExecutor)
线程池体系图:
Executor
java
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
对于Executor只提供了最简单的执行方法。
ExecutorService
ExecutorService提供了更多的状态判断。
ThreadPoolExecutor
这个线程池是最常用的方法之一。
java
@Test
public void Test1(){
ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,1, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
MyRunnable myRunnable = new MyRunnable();
threadPoolExecutor.execute(myRunnable);
threadPoolExecutor.execute(myRunnable);
}
ThreadPoolExecutor的核心构造函数:
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数名 | 作用 |
---|---|
corePoolSize | 核心线程池基本大小,核心线程数 |
maximumPoolSize | 线程池最大线程数 |
keepAliveTime | 线程空闲后的存活时间 |
TimeUnit unit | 线程空闲后的存活时间单位 |
BlockingQueue workQueue | 存放任务的阻塞队列 |
ThreadFactory threadFactory | 创建线程的工厂 |
RejectedExecutionHandler handler | 当阻塞队列和最大线程池都满了之后的饱和策略 |
-
corePoolSize(核心线程数):
- 这是线程池的基本大小,也就是在没有任务执行时线程池的大小,并且是线程池允许创建的最小线程数量。
- 当新任务提交给线程池时,如果当前运行的线程数少于
corePoolSize
,线程池会即使没有空闲线程也会创建一个新线程来处理请求。 - 这些核心线程会一直存活在线程池中,即使它们处于闲置状态(除非设置了
allowCoreThreadTimeOut
为true
)。
-
maximumPoolSize(最大线程数):
- 这是线程池允许创建的最大线程数。
- 当池中的线程数
>=corePoolSize
,且任务队列已满时。线程池会创建新线程来处理任务,直到线程数达到maximumPoolSize
。 - 如果运行的线程数已经达到了
maximumPoolSize
,新提交的任务会被拒绝处理,或者按照配置的拒绝策略来处理。 - 一旦线程数超过了
corePoolSize
,多余的空闲线程会在一定时间内(由keepAliveTime
参数指定)终止,以减少资源消耗。
举例来说,如果
corePoolSize
设置为10,maximumPoolSize
设置为20,那么任何时候线程池至少会有10个活跃的线程(如果有任务需要执行),即使有些线程是闲置的。当任务数增加,并且所有的核心线程都在忙时,线程池可以临时创建更多的线程,最多到20个。一旦这些额外的线程在完成任务后闲置了超过keepAliveTime
设定的时间,它们将会被终止。
-
workQueue:阻塞队列
- 当线程池正在运行的线程数量已经达到
corePoolSize
,那么再通过execute
添加新的任务则会被加workQueue
队列中,在队列中排队等待执行,而不会立即执行。一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue
,LinkedBlockingQueue
,SynchronousQueue
;
- 当线程池正在运行的线程数量已经达到
-
keepAliveTime:线程空闲时间
- 当线程空闲时间达到
keepAliveTime
时,线程会退出,直到线程数量=corePoolSize
- 如果
allowCoreThreadTimeout=true
,则会直到线程数量=0
- 当线程空闲时间达到
-
threadFactory:线程工厂,主要用来创建线程
-
rejectedExecutionHandler:任务拒绝处理器,两种情况会拒绝处理任务
1:当线程数已经达到maxPoolSize
,且队列已满,会拒绝新任务
2:当线程池被调用shutdown()
后,会等待线程池里的任务执行完毕,再shutdown
。如果在调用shutdown()
和线程池真正shutdown
之间提交任务,会拒绝新任务
3:当拒绝处理任务时线程池会调用rejectedExecutionHandler
来处理这个任务。如果没有设置默认是AbortPolicy
,另外在ThreadPoolExecutor
类有几个内部实现类来处理这类情况
ThreadPoolExecutor.AbortPolicy
:丢弃任务并抛出RejectedExecutionException
异常。
ThreadPoolExecutor.CallerRunsPolicy
:由调用线程处理该任务
ThreadPoolExecutor.DiscardPolicy
:也是丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
源码剖析
execute详解
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
//线程池创建失败,核心线程小于0?开什么玩笑,最大线程数还小于核心线程数不是扯蛋吗,还有哪有存活时间小于0的呀,肯定创建失败咯
throw new IllegalArgumentException();
//没有把等待队列或者创建线程的线程工厂或者线程要去处理的方法传递过来,那怎么创建线程呢?,也得润
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
//设置核心线程数、最大线程数、等待队列、存活时间、线程工厂、处理方法
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
现在类初始化好了,得去看看执行器executor了
java
threadPoolExecutor.execute(myRunnable);
java
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果当前线程数量小于核心线程数量
if (workerCountOf(c) < corePoolSize) {
//true表示添加的为核心线程数量
if (addWorker(command, true))
return;
c = ctl.get();
}
//是否是运行状态,并且将需要执行的方法成功加入到等待队列当中
if (isRunning(c) && workQueue.offer(command)) {
//再次检查状态
int recheck = ctl.get();
//发现线程池已经关闭了,移除方法
if (!isRunning(recheck) && remove(command))
//调用rejectedExecution
reject(command);
else if (workerCountOf(recheck) == 0)
//如果没有可用线程的话(比如coreSize=0),创建一个空work
//该work创建时不会给指派任务(为null),但是会被放入works集合,进而从队列获取任务去执行
addWorker(null, false);
}
else if (!addWorker(command, false))
//添加线程失败了
reject(command);
}
这里的command就是我们要处理的myRunnable方法。
java
int c = ctl.get();
在Java的
ThreadPoolExecutor
类中,ctl
是一个AtomicInteger
实例,用来控制线程池的主要状态和线程数。它将这两类信息打包成一个整数以实现原子操作,这是为了线程安全性考虑,即在多线程环境中保证数据的一致性。这里的
int c = ctl.get();
这行代码是在获取当前的ctl
值。该值包含两部分信息:
- 线程池状态(例如 RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED)
- 工作线程数(当前活动的线程数)
ctl
使用位运算来存储这两部分信息。高位通常存储线程池状态,低位存储工作线程数 。这样设计是为了允许在单个原子操作中更新和检查线程池状态和线程数,可以保证线程安全。举例来说,如果你想要添加一个新线程到线程池时,需要检查当前活动的线程数是否少于配置的最大线程数。如果要关闭线程池,需要检查线程池的当前状态。所有这些操作都可以通过原子地读取
ctl
的值来进行。因此,
ctl.get()
是在执行任何需要知道线程池状态和线程数的操作之前,线程安全地获取这些信息的方法。
tex
为什么需要检测两次?
为了防止并发问题,int recheck = ctl.get();
这里再次获取线程池的当前状态。在并发环境中,线程池的状态可能在任务提交和放入队列后发生了变化。即使刚刚检查过线程池是运行的,现在可能不是了。
通常情况下,当你调用 shutdown()
方法时,线程池会继续执行并完成所有已经在等待队列中的任务,但它不会接受任何新的任务。shutdown()
方法启动了线程池的关闭序列,但这个关闭是渐进的,不是立即终止当前执行的任务。
那为什么还需要从队列中移除任务并拒绝它们呢?这个问题的关键是在于并发和时间窗口。考虑以下的并发场景:
- 线程A调用
shutdown()
方法,线程池开始关闭流程。 - 同时,线程B提交了一个任务到线程池,而这时线程池可能还没有完全转换到关闭状态。如果任务成功地被添加到等待队列中,那么按照
shutdown()
的语义,这个任务实际上是不应该被接受的。 - 线程B在提交任务后,再次检查线程池状态(这是一个好的并发编程实践,确保操作的有效性)。如果此时线程B发现线程池已经关闭(可能是由线程A触发的),它就知道了自己的任务不应该被队列接受。
- 因此,线程B会尝试移除刚刚添加的任务,以保持状态一致性,并触发拒绝处理程序(可能是记录日志、通知用户等)。
在这种情况下,shutdown()
方法之后再次检查线程池状态和移除任务是必要的步骤,以保证线程池关闭操作的一致性和正确性,即使在多线程竞争条件下。它确保了即使在关闭命令之后的短时间内提交的任务也会被正确处理,即不会执行,因为在 shutdown()
之后提交的任务是不应该被执行的。
进程创建addWorker
java
//线程创建
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//第一步,计数判断,不符合条件打回false
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//判断线程数,注意这里!
//也就说明线程池的线程数是不可能设置任意大的。
//最大29位(CAPACITY=29位二进制)
//超出规定范围,返回false,表示不允许再开启新工作线程,创建worker失败!
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))//core为true,就是核心线程数,反之就是最大线程数
return false;
//增加线程数量成功
if (compareAndIncrementWorkerCount(c))
break retry;//去执行第二步
c = ctl.get(); // 再次获取状态
if (runStateOf(c) != rs)
//状态发生了改变
continue retry;//回到第一步
// else CAS failed due to workerCount change; retry inner loop
}
}
//第二步,创建新work放入线程集合works(一个HashSet)
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//符合条件,创建新的work并包装task
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//加锁,workers是一个hashset(非线程安全),这里要保障线程安全性
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
//获取当前运行状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock. 统一管理所有的worker
*/
//private final HashSet<Worker> workers = new HashSet<Worker>();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
//更新当前线程池的最大线程数
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//注意,只要是成功add了新的work,那么将该新work立即启动,任务得到执行
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
java
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
Runnable firstTask;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
这里的Worker对象实现了Runnable接口的run方法,this.thread = getThreadFactory().newThread(this);
创建线程的this其实也就是worker自己。
而在run方法里面调用了runWorker,其实也就是运行线程了。
java
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();//获取当前线程
Runnable task = w.firstTask;//获取传递过来的要执行的方法
w.firstTask = null;
w.unlock(); // 允许被中断
boolean completedAbruptly = true; //默认是被打断了
try {
//task不为空执行task,反之去等待队列中拿一个任务出来run
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
//运行状态至少为STOP || (线程是否中断,清除中断状态&&线程至少为STOP)&&检查当前工作线程(wt)是否没有被中断。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
//中断当前这个工作线程
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//运行任务
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
p } catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
java
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
//判断当前线程是否需要被淘汰
//1.是否允许核心线程被淘汰 2.工作线程数是否大于核心线程数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//线程数超出max,并且上次循环中poll等待超时了,那么说明该线程已终止
//将线程队列数量原子性减
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//计数器做原子递减,减少线程数,返回null,for被中止,
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
//在keepAliveTime-TimeUnit.NANOSECONDS时间内没有任务拿出来就return null
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//没有任务就会一直阻塞,直到有任务为止
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
Executors
以上构造函数比较多,为了方便使用,juc提供了一个Executors工具类,内部提供静态方法
1)newCachedThreadPool( ) : 弹性线程数
2)newFixedThreadPool(int nThreads) : 固定线程数
3)newSingleThreadExecutor( ) : 单一线程数
4)newScheduledThreadPool(int corePoolSize) : 可调度,常用于定时