ThreadPoolExecutor execute() 工作原理

自定义线程池:

public class ThreadPoolTest {

    public static void main(String[] args) {
        //创建等待队列
        BlockingQueue<Runnable> bqueue= new ArrayBlockingQueue<Runnable>(20);
        //创建线程池,池中保存的线程数为3,允许的最大线程数为5
        ThreadPoolExecutor pool=new ThreadPoolExecutor(3,5,50, TimeUnit.MILLISECONDS,bqueue);

        Runnable t1=new MyThread();
        Runnable t2=new MyThread();
        Runnable t3=new MyThread();
        Runnable t4=new MyThread();
        Runnable t5=new MyThread();
        Runnable t6=new MyThread();
        Runnable t7=new MyThread();

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);
        pool.execute(t5);
        pool.execute(t6);
        pool.execute(t7);
        //关闭线程池
        pool.shutdown();

    }

}
class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"正在执行。。。");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

七个任务是在线程池的三个线程上执行的。说明下用到的ThreadPoolExecuror类的构造方法中各个参数的含义:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
                    TimeUnit unit,BlockingQueue<Runnable> workQueue) {
       
    }
corePoolSize:线程池中所保存的核心线程数,包括空闲线程。
maximumPoolSize:池中允许的最大线程数。
keepAliveTime:线程池中的空闲线程所能持续的最长时间。
unit:持续时间的单位。
workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。

线程池execute()工作的逻辑描述如下:

  1. 如果当前运行的线程,少于corePoolSize,则创建一个新的线程来执行任务。
  2. 如果运行的线程等于或多于 corePoolSize,将任务加入 BlockingQueue。
  3. 如果 BlockingQueue 内的任务超过上限,则创建新的线程来处理任务。
  4. 如果创建的线程数是单钱运行的线程超出 maximumPoolSize,任务将被拒绝策略拒绝。

在了解 execute() 方法之前,需要了解一下 clt

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  • 线程池的ctl是一个原子的 AtomicInteger。
  • 这个ctl包含两个参数 :(1) workerCount 激活的线程数; (2) runState 当前线程池的状态;
  • 它的低29位用于存放当前的线程数, 因此一个线程池在理论上最大的线程数是 536870911; 高 3 位是用于表示当前线程池的状态, 其中高三位的值和状态对应如下:
高三位状态描述
111RUNNING接受新任务并处理排队的任务
000SHUTDOWN不接受新任务,但处理排队的任务
001STOP

不接受新任务,不处理排队的任务,中断正在进行的任务

010TIDYING

所有任务都已终止,workerCount为零,线程转换状态为TIDYING,将运行terminated()钩子方法

110TERMINATEDTERMINATED()已执行完成

为了能够使用 ctl 线程池提供了三个方法:

//获取线程池的状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//获取线程池的工作线程数
private static int workerCountOf(int c)  { return c & CAPACITY; }
//根据工作线程数和线程池状态获取 ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }

 execute()

public void execute(Runnable command) {
       if (command == null)
           throw new NullPointerException();
       int c = ctl.get();

       //如果工作线程数小于核心线程数,
       if (workerCountOf(c) < corePoolSize) {
           //执行addWork,提交为核心线程,提交成功return。提交失败重新获取ctl
           if (addWorker(command, true))
               return;
           c = ctl.get();
       }

       //如果工作线程数大于等于核心线程数,则检查线程池状态是否是正在运行,且将新线程向阻塞队列提交。
       if (isRunning(c) && workQueue.offer(command)) {

           //recheck 需要再次检查,主要目的是判断加入到阻塞队里中的线程是否可以被执行
           int recheck = ctl.get();

           //如果线程池状态不为running,将任务从阻塞队列里面移除,启用拒绝策略
           if (! isRunning(recheck) && remove(command))
               reject(command);
           // 如果线程池的工作线程为零,则调用addWoker提交任务
           else if (workerCountOf(recheck) == 0)
               addWorker(null, false);
       }

       //添加非核心线程失败,拒绝
       else if (!addWorker(command, false))
           reject(command);
   }

 

 addWorker()

private static final int COUNT_BITS = Integer.SIZE - 3;  //32-3
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 2的29次方-1
//线程池最大大小。注意,实际最大值在内部以CAPACITY为界限。
private volatile int maximumPoolSize;

/**
 * core:是否核心线程
 */
private boolean addWorker(Runnable firstTask, boolean core) {
	retry:
	for (; ; ) {
		int c = ctl.get();  //ctl的值
		int rs = runStateOf(c);//线程池的状态

		//判断是否可以添加任务
		if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
			return false;

		for (; ; ) {
			int wc = workerCountOf(c); //工作线程数
			//是否大于最大值,是否大于核心线程数、是否大于最大线程数
			if (wc >= CAPACITY ||
					wc >= (core ? corePoolSize : maximumPoolSize))
				return false;
			//CAS增加工作线程数
			if (compareAndIncrementWorkerCount(c))
				break retry;
			c = ctl.get();
			//如果线程池状态改变,回到开始重新来
			if (runStateOf(c) != rs)
				continue retry;
		}
	}
	boolean workerStarted = false;
	boolean workerAdded = false;
	Worker w = null;
	//上面的逻辑是考虑是否能够添加线程,如果可以就cas的增加工作线程数量
	//下面正式启动线程
	try {
		//新建worker
		w = new Worker(firstTask);

		//获取当前线程
		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状态
				// 或者线程处于SHUTDOWN状态,且firstTask == null(可能是workQueue中仍有未执行完成的任务,创建没有初始任务的worker线程执行)
				if (rs < SHUTDOWN ||
						(rs == SHUTDOWN && firstTask == null)) {
					// 当前线程已经启动,抛出异常
					if (t.isAlive()) // precheck that t is startable
						throw new IllegalThreadStateException();
					//workers 是一个 HashSet 必须在 lock的情况下操作。
					workers.add(w);
					int s = workers.size();
					//设置 largeestPoolSize 标记workAdded
					if (s > largestPoolSize)
						largestPoolSize = s;
					workerAdded = true;
				}
			} finally {
				mainLock.unlock();
			}
			//如果添加成功,启动线程
			if (workerAdded) {
				t.start();
				workerStarted = true;
			}
		}
	} finally {
		//启动线程失败,回滚。
		if (!workerStarted)
			addWorkerFailed(w);
	}
	return workerStarted;
}

execute() 中有三处调用了 addWork() 我们逐一分析。

  • 第一次,条件 if (workerCountOf(c) < corePoolSize) 这个很好理解,工作线程数少于核心线程数,提交任务。所以 addWorker(command, true)
  • 第二次,如果 workerCountOf(recheck) == 0 如果worker的数量为0,那就 addWorker(null,false)。为什么这里是 null ?之前已经把 command 提交到阻塞队列了 workQueue.offer(command) 。所以提交一个空线程,直接从阻塞队列里面取就可以了。
  • 第三次,如果线程池没有 RUNNING 或者 offer 阻塞队列失败,addWorker(command,false),很好理解,对应的就是,阻塞队列满了,将任务提交到,非核心线程池。与最大线程池比较。

重新归纳execute()的逻辑应该是:

  1. 如果当前运行的线程,少于corePoolSize,则创建一个新的线程来执行任务。
  2. 如果运行的线程等于或多于 corePoolSize,将任务加入 BlockingQueue。
  3. 如果加入 BlockingQueue 成功,需要二次检查线程池的状态如果线程池没有处于 Running,则从 BlockingQueue 移除任务,启动拒绝策略。
  4. 如果线程池处于 Running状态,则检查工作线程(worker)是否为0。如果为0,则创建新的线程来处理任务。如果启动线程数大于maximumPoolSize,任务将被拒绝策略拒绝。
  5. 如果加入 BlockingQueue 。失败,则创建新的线程来处理任务。
  6. 如果启动线程数大于maximumPoolSize,任务将被拒绝策略拒绝。

 参考地址:https://www.xilidou.com/2018/02/09/thread-corepoolsize/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值