Java线程池原理

1、为什么要使用线程池

  当前主流商业Java虚拟机的线程模型都是基于操作系统原生线程模型来实现,即采用1:1的线程模型。

  如果不理解可以看这篇文章:java与线程

  现在的服务器基本都是多个核心,多个核心可以达到并行运算。对于我们常见的web服务,有cpu密集型计算,也有IO密集型计算,线程数设置过多,会导致线程上下切换过于频繁,消耗性能,设置过少,不能充分利用cpu,部分线程处于等待状态。合理的设置线程数,可以充分提高cpu使用效率。其次,每次任务来,线程都进行创建、销毁也造成不必要的性能浪费。

总结,为什么要使用线程池?

  1. 降低资源消耗。避免线程重复创建、销毁造成不必要的开销
  2. 提高了响应速度。当任务来了,不必等待线程创建利用活跃线程、或唤醒沉睡线程去执行
  3. 合理的设置并发线程数,提高cpu使用效率。

2、线程池的生命周期

  JDK中线程池的核心实现类是ThreadPoolExecutor。它的参数如下:

参数说明
corePoolSize核心线程数量
maximumPoolSize线程池维护线程的最大数量
keepAliveTime线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁
unitkeepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS
workQueue线程池所使用的任务缓冲队列
RejectedExecutionHandler当缓冲队列已满,线程数也达到最大线程数时,执行拒绝策略

RejectedExecutionHandler 几种默认拒绝策略:

策略功能说明
AbortPolicy(中止策略)当触发拒绝策略时,直接抛出拒绝执行的异常,中止策略的意思也就是打断当前执行流程
CallerRunsPolicy(调用者运行策略)当触发拒绝策略时,只要线程池没有关闭,就由提交任务的当前线程处理
DiscardPolicy(丢弃策略)直接静悄悄的丢弃这个任务,不触发任何动作
DiscardOldestPolicy(弃老策略)果线程池未关闭,就弹出队列头部的元素,然后新任务加入队列
自定义策略自己实现拒绝策略功能

  ThreadPoolExecutor通过以 ctl 字段对线程池的运行状态线程池中有效线程的数量进行控制。它同时包含两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),高3位保存 runState,低29位保存 workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。

  同时它内部封装的获取线程池状态、获取线程池线程数量的方法。如以下代码所示:


	// 29
    private static final int COUNT_BITS = Integer.SIZE - 3; 
	// 536870911  二进制原码:0001 1111  1111 1111  1111 1111 1111 1111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1; 
		
	// ctl的前3位用来识别当前线程池状态,后29位记录线程数量。初始化是Running状态
	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

	// 这个方法计算取ctl的前3位值,后29位都置为0,得到当前线程池状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
	// 这个方法计算取ctl的后29位值,前3位都置为0,得到当前线程数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
	// 通过状态和线程数生成ctl
    private static int ctlOf(int rs, int wc) { return rs | wc; }			
	

	// 线程的5种状态
	// 二进制原码:1010 0000  0000 0000  0000 0000 0000 0000
	// 二进制补码:1110 0000  0000 0000  0000 0000 0000 0000
    private static final int RUNNING    = -1 << COUNT_BITS;
	// 二进制原码:0000 0000  0000 0000  0000 0000 0000 0000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
	// 二进制原码:0010 0000  0000 0000  0000 0000 0000 0000
    private static final int STOP       =  1 << COUNT_BITS;
	// 二进制原码:0100 0000  0000 0000  0000 0000 0000 0000
    private static final int TIDYING    =  2 << COUNT_BITS;
	// 二进制原码:0110 0000  0000 0000  0000 0000 0000 0000
    private static final int TERMINATED =  3 << COUNT_BITS;

  关于线程池的状态,有5种,分别是:

运行状态状态描述
RUNNING运行状态,能接受新提交的任务,并且也能处理阻塞队列中的任务
SHUTDOWN关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
STOP停止状态,不能接受新提交的任务,抛弃阻塞队列中的任务,会中断正在处理任务的线程
TIDYING清空状态,所有任务都已终止,workerCount(有效线程数)为0
TERMINATED终止状态,terminated()方法执行完后进入该状态

  其生命周期转换如下所示:

在这里插入图片描述

  线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。

3、线程池整体工作流程

  向线程池提交任务是由execute方法完成的,接下来我们通过分析源码来解析线程池整个提供任务的工作流程。

ThreadPoolExecutor.execute

  这里删除了官方注释,整个方法就短短几行。

public void execute(Runnable command) {
    	// 1.任务非空判断
        if (command == null)
            throw new NullPointerException();
       
        int c = ctl.get();
    	// 2.添加核心线程执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    	// 3.任务添加进阻塞队列等待被调用
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 线程池不再运行,则删除任务
            if (!isRunning(recheck) && remove(command))
                reject(command);
            // 因为核心线程数可以修改,这里添加任务成功后判断是否有线程可以执行任务,没有则添加线程   
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
    	// 4.添加非核心线程执行任务
        else if (!addWorker(command, false))
            // 5.如果失败,执行拒绝策略
            reject(command);
    }

  该方法的主要逻辑是:

  1. 新提交的任务进行非空校验
  2. 判断当前线程数 是否小于 corePoolSize。如果是,则新增核心线程执行任务
  3. 如果否,则把任务放进 workQueue。
  4. 如果放不下,则会添加非核心线程执行任务。
  5. 如果添加失败,则执行拒绝策略

这里会有个问题,为什么步骤3 要双重校验线程池状态?(双重检验:修改之前检验,修改之后检验)

答:第一次校验合格,再往workQueue添加任务,这些操作不是原子性的,也就意味着在第一次校验之后和添加任务成功之前,线程池状态是可以被改变的。所以这里在添加成功后,再次进行线程池状态校验。

  整体流程图如下:

在这里插入图片描述

ThreadPoolExecutor.addWorker

  这个方法主要添加工作线程,如果firstTask不为null,则执行firstTask;否则,去阻塞队列获取任务执行。

private boolean addWorker(Runnable firstTask, boolean core) {
    	// 位置标记,嵌套循环调用 continue retry; 能重新回到这里
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
			
			/**
				1.如果线程池状态是 RUNNING, 继续往下执行
				2.如果线程池状态是 SHUTDOWN, 如果first != null,返回false。
            	因为 SHUTDOWN状态不允许添加新的任务,但是允许添加线程去执行任务
           		3.如果线程池状态>= SHUTDOWN,返回false。因为 STOP状态 不能添加新任务,
           		阻塞队列中任务排空,线程开始释放
			*/
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
			
            
            for (;;) {
				
				/**
					1.当前线程数 大于 允许最大线程数,返回 false
					2.如果添加的是核心线程,当前线程数 >= 核心线程数,返回 false
					3.如果添加的是非核心线程,当前线程数 >= 最大线程池容量,返回 false
				*/
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                
                // CAS操作修改 当前线程数+1。保证数据安全,一致性
                if (compareAndIncrementWorkerCount(c))
                    // 操作成功 打破嵌套循环
                    break retry;
                
                // cas操作增加线程数失败
                c = ctl.get(); 
                // 这里判断 线程池状态是否变化
                if (runStateOf(c) != rs)
                    // 变化了 跳到最外层循环
                    continue retry;
                
                // 没变化,则继续内循环
            }
        }
        /**
		   这2个循环,直至添加线程成功or失败	
    	   失败则返回 fasle
    	   成功则继续往下执行
    	   值得一提的是,上面操作只是线程池状态、数量检验,然后修改线程数量,
    	   并没有实际的创建线程
		*/
    	
    	/** 下面开始创建线程,并添加到线程容器,便于回收 */
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 重点 创建Worker线程 封装了工作线程、初始任务
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                
                // 注意这里为啥要加锁
                mainLock.lock();
                try {
                    
                    int rs = runStateOf(ctl.get());
					/**
					 	1.如果线程池状态是 Runnings,添加线程到works
					 	2.如果线程池状态是 SHUTDOWN,初始任务是null,
					 	才可以添加线程到 works
					*/
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        
                        /**
						  workers是个HashSet集合,添加也是线程不安全。
						  为什么用set?后续删除可以根据hash删除引用,被垃圾回收。
						*/
                        workers.add(w);
                        
                        int s = workers.size();
                        // 这个操作是线程不安全的
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        
                        workerAdded = true;
                    }
                } finally {
                    // 放锁
                    mainLock.unlock();
                }
                
                // 如果添加成功,则启动线程
                if (workerAdded) {
                	/**
					在创建 Worker 时,worker 有 thread 的引用,
					同时 thread.Runnable 也引用了 worker,
					所有最终还是会调用 woreker.run
					*/
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

  通过该方法申请线程,会有两种申请方式,如下图所示:

在这里插入图片描述

4、Worker线程和workQueue任务队列

  线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。而workQueue任务队列就是充当生产者角色,当有任务添加进来,会通知处于waiting的线程去任务队列获取任务,而Worker线程在这里充当了消费者。

4.1 Worker线程(消费者)

  Worker 类主要是维护线程运行任务时的中断控制状态,以及次要的信息记录。这里可中断状态主要针对是 SHUTDOWN 状态,它是由 AQS 的 state 字段控制的,怎么理解?SHUTDOWN 状态 调用中断方法需要先获取锁,CAS 操作修改 state = 0->1,修改成功才能执行 t.interrupt() 方法。

  Worker 类部分代码如下:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    final Thread thread;// Worker持有的线程,该线程同时拥有worker的引用
    Runnable firstTask;// 初始化的任务,可以为null
}

  Worker这个工作线程,实现了Runnable接口,并持有一个线程thread,一个初始化的任务firstTask,实现 Runnable 接口便于被线程引用,最终调用 worker.run() 方法。

  • thread:是在调用构造方法时通过ThreadFactory来创建的线程,可以用来执行任务,它的 Runnable 变量引用了 Worker;
  • firstTask:用它来保存传入的第一个任务,这个任务可以有也可以为 null。如果这个值是非空的,那么线程就会在启动初期立即执行这个任务;如果这个值是null,那么就需要去执行任务列表(workQueue)中的任务。

  Worker 是通过继承 AQS,使用 AQS 来实现独占锁这个功能。没有使用可重入锁 ReentrantLock,而是使用 AQS 实现了一个简单的非可重入互斥锁。这里问题来了,worker 在执行任务的时候为什么要加锁?

  答:主要针对线程池状态从 RUNNING——>SHUTDOWN 转换时,需要尝试中断闲置的线程,但又不能中断正在执行任务的线程,所以Worker 通过 AQS 实现非重入独占锁来确保正在执行任务的线程不会被中断。(这里可能有人会问为什么要中断闲置线程?可以先思考下,文章末尾有答案)

  1. lock 方法一旦获取了独占锁,表示当前线程正在执行任务中,不是空闲状态,则不应该中断线程(PS:如果任务业务代码里面有 wait、sleep 等操作,如果工作线程被标记了中断,那么工作线程遇到这些操作则会中断抛出异常)
  2. 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程标识中断。
  3. 线程池在执行 shutdown 方法或 tryTerminate 方法时会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 方法会使用 tryLock 方法来判断线程池中的线程是否是空闲状态;如果线程是空闲状态则可以安全回收。(PS:tryLock 本质是实现了 AQS.tryAcquire 方法,与 ReentrantLock 不一样,它是实现了非重入功能)

  在线程池状态 RUNNING 转换为 SHUTDOWN 状态后,会调用该方法。shutdown()——>interruptIdleWorkers()。

/**
见名知意,中断闲置的线程。什么是闲置的线程?就是阻塞任务队列空了,
线程进入 AQS 等待队列的线程。
*/
private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // worker 创建后被添加到 workers(HashSet)
            for (Worker w : workers) {
                Thread t = w.thread;
                // 在中断之前先获取非重入独占锁
                if (!t.isInterrupted() && w.tryLock()) {
                    // tryLock 来判断线程是否在闲置。
                    try {
                    	// 给线程线程上中断标识
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

  整体流程图如下:

在这里插入图片描述

  而在线程池状态 RUNNING 转换为 STOP状态后,会调用该方法。shutdownNow()——>interruptWorkers()

// 见名思意,中断线程
private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
 
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

  可以看出,并不会去判断线程是否在闲置,而是直接标识线程中断。

  整体流程图如下所示:

在这里插入图片描述
  值得一提的是,shutdown() 和 shutdownNow() 方法,最终还会调用 tryTerminate(); 该方法更多的是做一些补偿措施,比如还有线程阻塞在 waiting 状态,则会再次遍历 works,调用 interrupt 方法让线程能顺利回收。

ThreadPoolExecutor.runWorker

  工作线程 start 后会调用这个方法,它包括了线程去阻塞队列获取任务、执行任务逻辑等。

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        /**
		 worker初始化时,state是设置为-1的,相当于加了锁;这里更新为0,相当于释放锁。			                            	允许线程中断
		*/
        w.unlock(); 
        boolean completedAbruptly = true;
        try {
        	/**
				这个循环很重要
				1. 判断worker初始化时的初始任务 不为空,就执行初始任务
				2. 如果初始任务为空,则去阻塞队列获取任务,获取到了,就往下执行;
				那如果获取不到任务怎么办?
			*/
            while (task != null || (task = getTask()) != null) {
                
                // 加锁,SHUTDOWN状态 正在执行任务,则不应该中断线程
                w.lock();
             
                // 1.如果线程池是 STOP 状态,应确保线程是被设置了中断标记
                // 2.如果线程池不是 STOP 状态,确保线程是非中断状态。
                
                /**
                对于判断条件2,线程池如果是 SHUTDOWN 状态,工作线程在执行任务之前
                是可能被设置为中断状态的。这种逻辑主要发生在 shutdown() 方法,
                当线程释放锁后,在执行getTask()任务时,这期间另外一个线程执行完了
                 shutdown() 方法,那么这个线程是会被标记上中断标识的。
				*/
                
                /**
                Thread.interrupted():不管是否设置了中断标识,
                都会清除当前线程的中断标识。如果原先有中断标识则返回true,
                如果没设有则返回false。
				*/
				/**
				对于判断条件2 这里出现双重检验校验逻辑,
				第一次校验如果不是 STOP 状态,然后去清除线程的中断标识,清除成功后,
				再次校验线程状态是不是 STOP 状态,为什么要再次检验是不是 STOP 状态?
				*/
                
                /**
				这里第一次检验和清除线程可中断标识之间不是原子操作的,
				会受其他线程干扰。所以在执行Thread.interrupted()返回true后,
				应该考虑在这之间有这种情况,有其他线程修改了线程池状态为 STOP,
				并给线程设置了中断标识(这种逻辑主要发生在 shutdownNow()方法),
				所以我们必须又给线程重新打上中断标识
				*/
                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;
                    } 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 {
            // 调用getTask()方法返回null时,会执行到这里,回收线程
            processWorkerExit(w, completedAbruptly);
        }
    }

  上面的代码中可以看到有 beforeExecute、afterExecute,它们都是钩子函数,可以分别在子类中重写它们用来扩展 ThreadPoolExecutor,例如添加日志、计时、监视或者统计信息收集的功能。

  工作线程在处理完任何后,它还会通过getTask()方法向阻塞队列获取任务。

ThreadPoolExecutor.getTask

  

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
			
            // 这个逻辑很重要,判断线程是不是可以回收就是在这里
            /**
			1.如果线程池是 SHUTDOWN 状态,阻塞任务队列也是空的,
			  那么workerCount - 1,返回 null,回收线程	
			2.如果线程池是 STOP 状态,那么workerCount - 1,返回null,回收线程
			*/   
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
			
            int wc = workerCountOf(c);

            /**
			allowCoreThreadTimeOut:默认未false,
			是否允许核心线程在超过 keepAliveTime 被回收,即都是非核心线程
			*/
            // time:true 表示当前线程是 非核心线程,false 当前线程 是核心线程
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
   			
			/**
			判断当前线程数量是否大于maximumPoolSize。线程池必须保证,不超过。
            为什么会有这种情况? 因为线程池对外提供了
            修改最大线程数量的方法 setMaximumPoolSize()
			*/
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                // cas操作失败继续循环
                continue;
            }

            try {
                
                Runnable r = timed ?
                    // 如果是非核心线程 进入这个方法,自动唤醒
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                 	// 如果是核心线程 进入这个方法,等待被唤醒
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

  workQueue.poll(),当非核心线程拿取不到任务时候,会自动唤醒,然后再进入 for (;;)循环,如果判断 workQueue.isEmpty(),那么会返回 null,最后这个非核心线程会被回收

  这个方法看起来很简单,定义了核心线程、非核心线程拿取任务的核心逻辑。

4.2 WorkQueue阻塞任务队列(生产者)

  接下来是另外一个重点,生产者 workQueue,线程的阻塞、唤醒都是靠它完成的(本质是靠 ReentrantLock 完成线程通信的)。它是一个接口,我们下文都以它的一个实现类 ArrayBlockingQueue 来解读。

ArrayBlockingQueue.take

  从上文可知,核心线程去阻塞队列拿取任务是调用这个方法的。看这个方法之前先思考一个问题,如果阻塞队列为空,线程会怎么办,自旋吗?

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        // 加锁,保证这是个线程安全的队列
        lock.lockInterruptibly();
        try {
            // 如果队列里面没有任务
            while (count == 0)
                // 重点这个方法。notEmpty = ReentrantLock.newCondition();
                // 底层就是 AQS$ConditionObject 来实现唤醒和通知功能
                notEmpty.await();
            
            // 取出队列里面的任务
            return dequeue();
        } finally {
            // 放锁
            lock.unlock();
        }
    }

  当workQueue中没任务时,线程挂起,进入 AQS 等待队列,等待被唤醒

  字段 notEmpty 是什么,AOS$ConditionObject,它是实现等待通知的关键所在,可以将当前线程挂起进入AQS等待队列尾。当被调用signal()通知方法后,则会进入AQS同步队列尾,等持有锁的线程释放锁的后,则会被真正唤醒,抢占锁,拿取任务,执行任务。如果不明白,AQS等待队列的实现,可以看这篇文章:深入理解AQS原理

  我们看下阻塞队列添加任务的方法是不是调用了signal()。

ArrayBlockingQueue.offer
public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
     	// 通知
        notEmpty.signal();
}  

  从这里可以看出,如果AQS等待队列有闲置线程,每添加一个任务,则会唤醒一个闲置线程。如果没闲置线程,等待被运行的线程获取并执行。

ArrayBlockingQueue.poll

  接下来我们分析,非核心线程去阻塞任务队列读取任务的方法

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
     // 加锁,保证这是个线程安全的队列
        lock.lockInterruptibly();
        try {
            
            while (count == 0) {
                // 队列里面没有任务
                if (nanos <= 0)
                    // 设置的超时 时间小于 0
                    // 直接返回null  回收线程
                    return null;
                
                // 进入限时等待状态,超过时间会被自动唤醒 or 被主动唤醒
                // 如果超时自动被唤醒,并且队列里面没有任务,则返回 null 线程被回收。(PS:这里是 非核心线程超时回收的逻辑所在)
                nanos = notEmpty.awaitNanos(nanos);
            }
            // 取出队列里面的任务
            return dequeue();
        } finally {
            // 放锁
            lock.unlock();
        }
    }

4.3 Worker线程和WorkQueue队列工作流程图:

在这里插入图片描述

5、附加:

为什么进入SHUTDOWN和STOP状态时,要给线程设置中断标识。

  • 当进入 SHUTDOWN、STOP 状态,对于已经挂起的工作线程进入了AQS等待队列,线程也就进入了无限等待 waiting 状态,这时阻塞任务队列是无法添加新的任务,也就永远无法唤醒它们。所以为了回收这部分线程,在调用 shutdown()、shutdownNow() 方法时,试图给这部分状态线程上中断标识,以便回收它们。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值