【Java学习笔记】多线程 Part 5 - 线程池

一、两种线程模型

1. 用户线程ULT

由用户程序实现,不依赖于操作系统。不需要用户态与核心态的切换,速度快。内核对用户线程无感知,线程阻塞则进程阻塞。

2. 内核线程KLT

由操作系统管理,线程阻塞不会引起进程阻塞。在多处理器下,多线程在多处理器上并行运行。效率比用户线程低。JVM基本用的是KLT。从Java创建的线程会1:1的对应到内核,然后由CPU调度执行。

二、为什么要有线程池

创建线程和销毁线程都是很耗资源的操作,而Java线程依赖内核线程,创建线程需要进行操作系统状态切换。为了避免过度消耗资源,有了线程池,作为线程缓存的区域。

三、线程池的好处

  1. 降低资源消耗
  2. 提高响应速度
  3. 方便管理
  4. 线程复用,可以控制最大并发数,管理线程

四、线程池使用

1. 三大使用

a). 单个线程

public class TestThreadPool {
	public static void main(String[] args) {
		ExecutorService threadPool =  Executors.newSingleThreadExecutor(); //单个线程
		try {
			for (int i = 0; i < 5; i++) {
				//通过线程池创建线程
				threadPool.execute(()->{
					System.out.println(Thread.currentThread().getName());
				});
			}
		} finally {
			threadPool.shutdown();
		}	
	}
}

输出:

pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1

b). 创建一个固定大小的线程池,这里大小设为3,所以最多会有3个线程执行

public class TestThreadPool {
	public static void main(String[] args) {
		ExecutorService threadPool =  Executors.newFixedThreadPool(3); //创建一个固定大小的线程池
		try {
			for (int i = 0; i < 5; i++) {
				//通过线程池创建线程
				threadPool.execute(()->{
					System.out.println(Thread.currentThread().getName());
				});
			}
		} finally {
			threadPool.shutdown();
		}	
	}
}

输出:

pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-3
pool-1-thread-1

c). 能有多少就有多少,这里输出创建了8个线程

public class TestThreadPool {
	public static void main(String[] args) {
		ExecutorService threadPool =  Executors.newCachedThreadPool();
		try {
			for (int i = 0; i < 10; i++) {
				//通过线程池创建线程
				threadPool.execute(()->{
					System.out.println(Thread.currentThread().getName());
				});
			}
		} finally {
			threadPool.shutdown();
		}	
	}
}

输出:

pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2
pool-1-thread-6
pool-1-thread-7
pool-1-thread-5
pool-1-thread-8
pool-1-thread-2
pool-1-thread-8

2. 七大参数

无论是single, fixed, Cached,通通调用的都是 ThreadPoolExecutor 类来进行线程池创建

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, //内置参数太大,容易OOM
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

我们来看一下 ThreadPoolExecutor 类构造器,参数列表里就是传说中的七大参数

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;
}

在阿里巴巴开发手册里面规定了,不要用Executors创建线程池,而是要用ThreadPoolExecutor来创建,因为确实用Executors不安全,有OOM问题。

3. 线程池结构

那么我们先来举个例子看一下线程池的结构,再来讨论着七大参数分别有什么意义。

比如银行办理业务。这个银行有5个柜台,但是一般情况下不会5个都开放,可能只开放3个。银行里还会有等候区域,如果客户过来发现有空闲的柜台,那他就可以直接去办理业务;如果三个开放柜台都有人在用了,那他就要去等候区等待。
在这里插入图片描述
但是银行的等待区域也是有一定容量的,如果等待区域满,那就说明来的客户很多了,那就要把没有开放的柜台也开放进行业务受理。但如果这个时候,还是有源源不断的客户进来,所有柜台都在受理,等待区域也占满了,那么这个时候外面来的客户可能就会被告知等会儿再来吧。之后过了高峰期,人流又会减少,那么右边那两个柜台发现很久没人来受理业务了,那它们就会关闭。
在这里插入图片描述
那么这个银行的场景就可以类比到线程池,并且对应我们的七大参数:

int corePoolSize,						//核心线程池大小
int maximumPoolSize,					//最大核心线程池大小
long keepAliveTime,						//超时了没有人调用就会释放
TimeUnit unit,							//时间单位
BlockingQueue<Runnable> workQueue,		//阻塞队列
ThreadFactory threadFactory,			//线程工厂,创建线程用的
RejectedExecutionHandler handler) {		//拒绝策略

那么对于这个银行,corePoolSize = 3, maximumPoolSize = 5
在这里插入图片描述

4. 四种拒绝策略

对于拒绝策略RejectedExecutionHandler,有四种实现:
在这里插入图片描述
默认的拒绝策略是AbortPolicy

private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

我们一个个来研究一下都是什么策略:

  1. AbortPolicy:直接拒绝,可以看到rejectedExecution方法里面直接抛出了一个异常
public static class AbortPolicy implements RejectedExecutionHandler {
    /**
     * Creates an {@code AbortPolicy}.
     */
    public AbortPolicy() { }

    /**
     * Always throws RejectedExecutionException.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     * @throws RejectedExecutionException always
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}
  1. CallerRunsPolicy:让线程执行它原有的业务,可谓从哪里来回哪里去。线程池不会受理它的业务。如果线程池正好这个时候被关闭了,那进来的这个线程就回不到原来的地方了。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code CallerRunsPolicy}.
     */
    public CallerRunsPolicy() { }

    /**
     * Executes task r in the caller's thread, unless the executor
     * has been shut down, in which case the task is discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}
  1. DiscardPolicy:可以看到rejectedExecution里什么也没做,所以进来的线程也是直接被拒绝,只不过不会报异常
public static class DiscardPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardPolicy}.
     */
    public DiscardPolicy() { }

    /**
     * Does nothing, which has the effect of discarding task r.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}
  1. DiscardOldestPolicy:这个和前三个有很大的不同。如果线程池关闭了,那当然什么也不会发生。如果线程池开着,那就把阻塞队列里面第一个排队的给移走,然后尝试让线程池受理新进来的这个线程。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardOldestPolicy} for the given executor.
     */
    public DiscardOldestPolicy() { }

    /**
     * Obtains and ignores the next task that the executor
     * would otherwise execute, if one is immediately available,
     * and then retries execution of task r, unless the executor
     * is shut down, in which case task r is instead discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

5. 怎么确定最大核心线程数

  • CPU密集型:把 maximumPoolSize 就设置为CPU核数
  • IO密集型:判断程序中十分占IO的任务有多少个,然后设置 maximumPoolSize 至少大于这个个数
//打印本机CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());

五、线程池的五种状态

//COUNT_BITS是29
private static final int RUNNING    = -1 << COUNT_BITS; //-1十六进制是ffffffff
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;
  1. Running:能接受新任务以及处理已添加的任务
  2. Shutdown:不接受新任务,但可以处理已经添加的任务
  3. Stop:不接受新任务,不处理已经添加的任务,并且中断正在处理的任务
  4. Tidying:所有的任务已经终止
  5. Terminated:线程池彻底终止

线程池状态转换
线程池状态转换
我们把这五种状态的数值整理一下:

状态
RUNNING1110 00000000 00000000 00000000 0000
SHUTDOWN0000 00000000 00000000 00000000 0000
STOP0010 00000000 00000000 00000000 0000
TIDYING0100 00000000 00000000 00000000 0000
TERMINATED0110 00000000 00000000 00000000 0000

我们发现五种状态被存在一个32位的Integer里。这里高3位代表状态,低29位代表当前工作的线程数

六、线程池工作原理

execute()

  1. 如果当前工作线程小于核心线程数,那就创建一个核心线程worker并且负责执行传入的command任务
  2. 如果当前工作线程大于核心线程数,那就把传入的command任务放入阻塞队列里
  3. 如果阻塞队列满了,传入的command无法放入,那就尝试增加非核心线程worker
  4. 如果非核心线程数也满了,那就使用拒绝策略,拒绝传入的command任务
  5. 以上情况都是建立在线程池running状态,如果线程池准备终止了,那传入的command不会被受理,也不会有新的worker被创建
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get(); 									//一开始拿到的是RUNNING状态 0xe0000000
    if (workerCountOf(c) < corePoolSize) { 				//第一次拿到的当前工作线程数是0,小于核心线程数
        if (addWorker(command, true)) 					//尝试增加一个线程去执行command的事务,如果返回false则增加失败
        	return;
        c = ctl.get();
    }
    
    if (isRunning(c) && workQueue.offer(command)) { 	//判断当前状态是不是running状态,并且将command加入阻塞队列中
	    int recheck = ctl.get();						//重新获取线程池状态
	    if (! isRunning(recheck) && remove(command))	//如果线程池不是running状态,那就将command从阻塞队列移出
	        reject(command);							//并且用相应拒绝策略拒绝command
	    else if (workerCountOf(recheck) == 0)			//如果当前工作线程为0,增加非核心线程
	        addWorker(null, false);
	}
	else if (!addWorker(command, false))				//如果阻塞队列满了,增加非核心线程
	    reject(command);								//如果非核心线程也满了,那就使用拒绝策略
}

addWorker() 上半部分

通过内外两层循环,先检查线程池当前可不可以增加新的worker,不可以增加新worker的情况有两大类:

  1. 线程池不在running状态
    a). 线程池处于Stop, Tidying, Terminated
    b). 线程池接受了新任务
    c). 阻塞队列为空
  2. 线程池在running状态
    a). 当前工作线程数已经达到了理论最大值0x1fffffff
    b). 当前工作线程数超过了核心线程数或者最大线程数(取决于参数core的值)

简单的说,如果线程池不在running状态了,那线程池要想创建新worker,必须是在线程池shutdown状态,因为这个时候线程池虽然不接受新任务,但之前已接受的任务还都会处理完。而且这个时候,创建的worker只会是非核心线程,而且只会从阻塞队列里去拿任务。如果线程池还在running状态,那线程池数量必须在要求的限制范围内。

检查后,确定可以创建新worker,那就通过CAS将工作线程数加1,真正创建新worker要到addWorker()的下半部分

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    //外层循环
    for (;;) { 
        int c = ctl.get(); 										//获取当前状态
        int rs = runStateOf(c); 								//取得前三位,也就是获取线程池状态,

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&									//如果线程池状态大于0(只有running状态是小于0的)
            ! (rs == SHUTDOWN &&								//如果线程池状态不是shutdown(shutdown是全0)
               firstTask == null &&								//或者传入的task非空
               ! workQueue.isEmpty()))							//或者阻塞队列是空
            return false;										//返回false
		
		//内层循环
		for (;;) {
            int wc = workerCountOf(c); 							//获取当前工作线程数
            if (wc >= CAPACITY || 								//如果当前工作线程数达到或者超过最大值
                wc >= (core ? corePoolSize : maximumPoolSize)) 	//或者当前工作线程数超过核心线程数(如果是非核心,则和最大线程数比较)
                return false;									//返回false
                
            if (compareAndIncrementWorkerCount(c)) 				//通过CAS增加工作线程数量,也就是线程池状态直接加1
                break retry; 									//如果CAS成功的话直接跳出外层循环
            
            													//如果能执行到这一行说明上一步的CAS没有成功
            c = ctl.get(); // Re-read ctl
            if (runStateOf(c) != rs) 							//如果前三位的线程池状态改变了,那就重新走外层循环
                continue retry;									//回到最开始retry的位置
            // else CAS failed due to workerCount change; retry inner loop 否则就重新走内层循环
        }
    }

addWorker() 下半部分

实质的创建了新worker,并且启动线程,让这个worker开始工作。当前线程启动的条件是线程池还处于running状态,或者处于shutdown状态而且没有接受新任务。最后返回布尔值代表线程是否成功启动。

    //外层循环
    	//内层循环 完成工作线程数加1
    
    //执行到这一行说明CAS成功,线程池的当前工作线程数已经更新
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask); 								//初始化Worker
        final Thread t = w.thread; 								//获取Worker对象的线程
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock; 		//加了一个Reentrantlock锁
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get()); 				//获取线程池当前状态
                if (rs < SHUTDOWN || 							//如果当前状态小于0 也就是处于running状态
                    (rs == SHUTDOWN && firstTask == null)) { 	//或者线程池处于shutdown状态以及task为空,走进if下面的语句
                    if (t.isAlive()) 							//如果线程已经启动,那就报异常
                        throw new IllegalThreadStateException();
                    workers.add(w); 							//workers是一个HashSet
                    int s = workers.size();						//获取workers的大小
                    if (s > largestPoolSize)					//记录最大的线程数量
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();										//启动线程
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);									//如果由于中断,而且这个线程还没启动,就会把这个线程给移除workers哈希表,以及将工作线程数量减1
    }
    return workerStarted;										//返回线程是否成功启动
}

看看Worker构造器,将传入的firstTask赋给Worker对象的Runnable变量,再将Worker对象的线程初始化,但注意,这时还没有将firstTask交给线程处理

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

Worker类继承了Runnable接口,真正启动线程调用的就是Worker类里的run方法

public void run() {
    runWorker(this);
}

runWorker()

如果worker的task是空的话,那就从阻塞队列里面去取task,并且这里是个while大循环,不断地去判断是不是有事务要做,实现了线程池的复用机制。如果在阻塞队列取task超时了,那就会跳出while往finally走,准备回收当前这个worker。

在阻塞队列取task的工作在getTask()里完成;回收worker的工作在processWorkerExit()里完成

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread(); //拿到当前线程
    Runnable task = w.firstTask;		//拿到业务
    w.firstTask = null;					//把worker里的firstTask置空
    w.unlock(); 						//一开始new Worker的时候,state设的是-1,这里解锁就是将state设为0
    boolean completedAbruptly = true;	//true表示当前线程没有正常结束
    try {
    	/** 循环复用,如果task是空的话,就从阻塞队列里面去取task
    	  * 如果阻塞队列里没东西了,线程就会阻塞在那里,如果阻塞过了一定时间,就会返回null
    	  * 然后跳出while循环,往finally走,准备回收当前这个worker
    	  */
        while (task != null || (task = getTask()) != null) { 
            w.lock();
            /** 
              * 如果线程池快已经是Stop或者Tidying或者Terminated了,那就中断当前线程
              * 否则就检查当前线程有没有被中断,没有被中断的话就继续往下走
              */
            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(); //这里调用了我们一开始调用线程池execute方法时候传进来的那个Runnable对象的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; //将task置空
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false; //能执行到这里说明阻塞队列获取task超时了,算是正常结束,准备回收worker
    } finally {
        processWorkerExit(w, completedAbruptly); //回收worker
    }
}

getTask()

从阻塞队列里去取task。有3种情况会决定要回收当前worker:

  1. 线程池处于shutdown,而且阻塞队列为空。不会接受新任务了,也没有旧任务做了,回收
  2. 线程池处于stop。新旧任务都不会做了,回收
  3. 如果当前线程数超过了核心线程数(默认核心线程不要求回收),并且从阻塞队列取task超时,回收

注意,如果核心线程也要求回收(取决于allowCoreThreadTimeOut),那核心线程超时了也一样会被回收,哪怕最后所有线程全都回收了。

如果这里决定要回收worker了,会预先把工作线程数减1,然后由processWorkerExit()真正执行回收工作。但如果由于中断等原因,这个worker应该要回收了,但工作线程数没有减1,那工作线程数减1的工作也会在processWorkerExit()里补充完成。

private Runnable getTask() {
    boolean timedOut = false; //用于判断从阻塞队列取task是否超时,超时为true

    for (;;) {
        int c = ctl.get();			//得到线程池状态
        int rs = runStateOf(c);		//得到线程池运行状态

        /** 这几种情况会执行worker数量减1,也就是回收worker
          * 1. 如果线程池处于shutdown,而且阻塞队列为空
          * 2. 如果线程池处于stop
          */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount(); //worker数量减1,这个操作直接改变线程池状态值
            return null;			//返回null,回到runWorker方法,准备回收当前worker
        }

		//执行到这一步说明线程池是在running状态,worker数量也没有减1
        int wc = workerCountOf(c); //得到当前工作线程数量

        //allowCoreThreadTimeOut表示核心线程如果空闲超过keepAliveTime的话,要不要进行回收,默认为false,不需要回收
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; //如果当前工作线程数量超过核心线程数则为true
		
		/** 3. 如果当前线程数超过最大线程数或者超过核心线程数,并且从阻塞队列取task超时,则把工作线程数worker减1
		  */
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null; //返回null,回到runWorker方法,准备回收当前worker
            continue;
        }
		
		//能执行到这里,说明worker还没有要被进行回收
        try {
            Runnable r = timed ? 
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : 	//timed为true代表线程数超过了核心线程数,或者核心线程也需要超时回收
                workQueue.take(); 										//timed为false代表当前worker是核心线程,而且不需要超时回收,所以如果它闲置着就让它一直等下去
            if (r != null)
                return r; 		//取到task
            timedOut = true; 	//超时了,没取到task
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

processWorkerExit()

如果线程是非正常结束的,那就会先将工作线程数减1。然后从workers哈希表中移除当前这个worker。再尝试终止线程池。如果当前线程池处在shutdown或者running状态,那有两种情况可能会需要再次补充创建非核心线程:

  1. 当前worker是非正常结束的
  2. 当前worker是正常结束的,但当前线程池工作线程数量小于最少需求数
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    /** 如果completedAbruptly为true说明线程非正常结束,可能由中断引起,那就先工作线程数减1
      * 如果是正常结束的话,在getTask()里已经完成了工作线程数减1
      */
    if (completedAbruptly) 
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w); //从哈希表中移除worker
    } finally {
        mainLock.unlock();
    }

    tryTerminate(); //尝试终止线程池

    int c = ctl.get(); //得到当前线程池状态
    if (runStateLessThan(c, STOP)) { //如果是shutdown或者running状态
        if (!completedAbruptly) { //如果线程是正常结束
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize; //找到当前线程最少需要的线程数
            if (min == 0 && ! workQueue.isEmpty()) //如果核心线程要求也被回收,然而阻塞队列非空,那至少留1个线程
                min = 1;
            if (workerCountOf(c) >= min) //如果当前工作线程数量满足最少的数量要求,那就直接return,否则还需要增加工作线程
                return; //直接返回,回到runWorker(), 也就结束了当前线程
        }
        addWorker(null, false);//增加非核心工作线程
        //之后返回,回到runWorker(), 结束当前线程
    }
}

tryTerminate()

线程池可以结束的条件:

  1. 线程池不在running状态
  2. 线程池不在shutdown状态;或者线程池在shutdown状态,但阻塞队列为空

线程池具备可以结束的条件之后,一次会尝试中断一个闲置线程(如果线程正在工作那就不会被中断),然后被中断的线程就会被回收,再去中断另一个线程,直到所有线程都被回收,线程池开始终止。

final void tryTerminate() {
    for (;;) {
        int c = ctl.get(); //得到线程池当前状态
        if (isRunning(c) || //如果线程池在running状态,不能结束线程池
            runStateAtLeast(c, TIDYING) || //或者线程池在TIDYING或者TERMINATED状态,那已经快结束了,不需要什么操作
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) //或者线程池在shutdown状态,而且阻塞队列不为空,不能结束线程池
            return; //返回,还不能结束线程池
            
        //执行到这里,说明具备线程池结束条件
        if (workerCountOf(c) != 0) { //如果工作线程为0, 那说明全部回收好了
            interruptIdleWorkers(ONLY_ONE); //中断一个闲置线程(如果线程正在工作那就不会被中断),之后它会被回收,然后再去中断另一个,直到全部回收完毕
            return;
        }
		
		//线程全部回收完毕了
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { //CAS将线程池状态设为TIDYING
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0)); //CAS将线程池状态设为TERMINATED
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

shutdown()

将线程池转为shutdown状态,然后结束线程池

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess(); //检查关闭线程池的权限
        advanceRunState(SHUTDOWN); //通过CAS把线程池状态设为shutdown
        interruptIdleWorkers(); //中断所有的闲置线程
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate(); //尝试结束线程池
}

shutdownNow()

同样也是结束线程池,但和shutdown()不一样的是:

  1. shutdownNow()有返回值,返回的就是阻塞队列里没完成的任务
  2. 线程池状态先转为的是Stop
  3. 会中断所有的线程,包括正在工作的
public List<Runnable> shutdownNow() {
     List<Runnable> tasks;
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
         checkShutdownAccess();
         advanceRunState(STOP); //线程池状态设为stop
         interruptWorkers(); //中断所有线程,包括正在工作的线程
         tasks = drainQueue();
     } finally {
         mainLock.unlock();
     }
     tryTerminate();
     return tasks;
 }

七、总结

线程池原理简单的讲就和银行办理业务的流程很相似,一开始只有核心线程受理业务,核心线程全部开启了,任务就会进阻塞队列排队,阻塞队列排满了,就会开启非核心线程,非核心线程也满了,就会启动拒绝策略。

每个工作线程处理好手头的业务之后,都会循环的去阻塞队列里取新任务来执行,实现线程的复用。

任务减少的时候,非核心线程如果长时间没有拿到任务就会被回收。但是核心线程如果不要求回收,即使没有任务,它们也会一直待在线程池里。

需要注意的是,在线程池内部,其实并没有对每个线程进行核心/非核心线程的标记。核心/非核心线程仅仅是在数量上进行了定义,而这个数量的限制规定了我们什么时候要创建新的工作线程,什么时候回收旧的线程,以及线程池里应该留下多少线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值