线程池ThreadPoolExecutor

每当使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

Java中通过线程池使线程可以复用,就是线程执行完一个任务,并不被销毁,而是可以继续执行其他的任务

线程池的种类及使用场景

线程池有四种:newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool

1、Java中的ThreadPoolExecutor类

public class ThreadPoolExecutor extends AbstractExecutorService {
	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
	private static final int COUNT_BITS = Integer.SIZE - 3;
	private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
	// 线程池的状态 runState is stored in the high-order bits
	private static final int RUNNING    = -1 << COUNT_BITS;
	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;
	// 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; }
	
	//任务缓存队列,用来存放等待执行的任务
	private final BlockingQueue<Runnable> workQueue;
	//线程池的主要状态锁,对线程池状态、线程池大小等的修改都要使用这个锁
	private final ReentrantLock mainLock = new ReentrantLock();
	//用来存放工作集,包含线程池中所有工作线程worker的集合
	private final HashSet<Worker> workers = new HashSet<Worker>();
	private final Condition termination = mainLock.newCondition();
	//用来记录线程池中曾经出现过的最大线程数
	private int largestPoolSize;
	//用来记录已经执行完毕的任务个数
	private long completedTaskCount;
	//线程工厂,用来创建线程
	private volatile ThreadFactory threadFactory;
	//任务拒绝策略
	private volatile RejectedExecutionHandler handler;
	//线程存活时间
	private volatile long keepAliveTime;
	//是否允许为核心线程设置存活时间
	private volatile boolean allowCoreThreadTimeOut;
	//核心池的大小
	private volatile int corePoolSize;
	//线程池中最大能容纳的线程数
	private volatile int maximumPoolSize;
	
    //四个构造器(前三个都是在调用第四个)
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
           long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
      this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
           Executors.defaultThreadFactory(), defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
           long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
           ThreadFactory threadFactory) {
      this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
           threadFactory, defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
           long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
           RejectedExecutionHandler handler) {
      this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
           Executors.defaultThreadFactory(), handler);
    }
    
    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;
    }
    //execute方法是用来提交任务的
    public void execute(Runnable command) {
      if (command == null)
          throw new NullPointerException();
      int c = ctl.get();
      if (workerCountOf(c) < corePoolSize) {
          if (addWorker(command, true))
              return;
          c = ctl.get();
      }
      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);
      }
      else if (!addWorker(command, false))
          reject(command);
    }
    //shutdown()和shutdownNow()是用来关闭线程池的
    public void shutdown() {
      final ReentrantLock mainLock = this.mainLock;
      mainLock.lock();
      try {
          checkShutdownAccess();
          advanceRunState(SHUTDOWN);
          interruptIdleWorkers();
          onShutdown(); // hook for ScheduledThreadPoolExecutor
      } finally {
          mainLock.unlock();
      }
      tryTerminate();
    }
    
    public List<Runnable> shutdownNow() {
      List<Runnable> tasks;
      final ReentrantLock mainLock = this.mainLock;
      mainLock.lock();
      try {
          checkShutdownAccess();
          advanceRunState(STOP);
          interruptWorkers();
          tasks = drainQueue();
      } finally {
          mainLock.unlock();
      }
      tryTerminate();
      return tasks;
    }
	//省略了其他属性和方法
}
 
线程池的状态:  

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

AtomicInteger类中有个int类型的value属性,它是一个原子整数,是一个复核类型的成员变量,代表了ThreadPoolExecutor中的控制状态,借助高低位包装了两个概念:

  1. workerCount:线程池中当前活动的线程数量,占据value的低29位;
  2. runState:线程池运行状态,占据value的高3位,有RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED五种状态。
一个int型变量是4个字节(即4byte),用32位二进制位表示。workerCount是线程池中当前活动的线程数量,占据AtomicInteger类中value属性的低29位,runState是线程池运行状态,占据AtomicInteger类中value属性的高3位。在ThreadPoolExecutor类中,使用COUNT_BITS变量表示workerCount所占的位数(private static final int COUNT_BITS = Integer.SIZE - 3; @Native public static final int SIZE = 32; SIZE 是Integer类中的常量),用29位二进制数表示线程池中当前活动的线程数量。所以,理论上,线程池中最少有0个线程,最多有536870911(29个1表示的二进制位)个线程。
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
变量CAPACITY 就代表workerCount的上限。1左移29位,也就是00000000 00000000 00000000 00000001 --> 00100000 00000000 00000000 00000000,再减去1的话,就是 00011111 11111111 11111111 11111111,前三位代表线程池运行状态runState,所以这里workerCount的理论最大值就应该是29个1,即536870911。
如何从AtomicInteger中value属性获取线程池的运行状态和活动线程数呢
在ThreadPoolExecutor类中提供了runStateOf和workerCountOf方法,runStateOf方法的作用是获取线程池的运行状态,workerCountOf方法的作用是从线程池中获取当前活动的线程数量。在下面分析线程池实现原理时,在execute方法中会调用这两个方法。
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
由上面分析可知,CAPACITY的高三位是0,低29位全是1,所以,(c&~CAPACITY)的结果就是c的高3位,(c&CAPACITY)的结果就是c的低29位。另外,由于AtomicInteger类中value属性的高三位表示线程池运行状态,低29位表示线程池中当前活动的线程数量,因此,将AtomicInteger类中的value属性作为参数,传递给runStateOf方法就可以得到线程池的运行状态,传递给workerCountOf方法就可以得到线程池中当前活动的线程数量。
 
线程池有以下五种运行状态:
(1)RUNNING:接受新任务,并处理队列任务
private static final int RUNNING    = -1 << COUNT_BITS;
-1在Java底层是由32个1表示的,左移29位的话,即111 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为1的话,表示RUNNING状态,即-536870912。
(2)SHUTDOWN:不接受新任务,但会处理队列任务
private static final int SHUTDOWN   0 << COUNT_BITS;
0在Java底层是由32个0表示的,无论左移多少位,还是32个0,即000 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为0的话,表示SHUTDOWN状态,即0。
(3)STOP:不接受新任务,不会处理队列任务,而且会中断正在处理过程中的任务
private static final int STOP       1 << COUNT_BITS;
1在Java底层是由前面的31个0和1个1组成的,左移29位的话,即001 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为001的话,表示STOP状态,即536870912。
(4)TIDYING:所有的任务已结束,活动的线程数workerCount为0,线程过渡到TIDYING状态,将会执行terminated()钩子方法
private static final int TIDYING    2 << COUNT_BITS;
2在Java底层是由前面的30个0和后两位的10组成的,左移29位的话,即010 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为010的话,表示TIDYING状态,即1073741824。
(5)TERMINATED:terminated()方法已经完成
private static final int TERMINATED 3 << COUNT_BITS;
3在Java底层是由前面的30个0和后两位的11组成的,左移29位的话,即011 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为011的话,表示TERMINATED状态,即1610612736。

由上可知,运行状态的值按照RUNNING->SHUTDOWN->STOP->TIDYING->

TERMINATED顺序值是递增的,这些值之间的数值顺序很重要。随着时间的推移,运行状态单调增加,但是不需要经过每个状态。可能存在的线程池状态的转换如下所示:

(1)RUNNING -> SHUTDOWN:调用shutdown()方法后,或者线程池实现了finalize方法,在里面调用了shutdown方法,即隐式调用;

(2)(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()方法后;

(3)SHUTDOWN -> TIDYING:线程池和任务队列均为空时;

(4)STOP -> TIDYING:线程池为空时;

(5)TIDYING -> TERMINATED:terminated()钩子方法完成时。

当创建线程池后,初始时,线程池处于RUNNING状态;

如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TIDYING状态。

当terminated()钩子方法完成时,线程池由TIDYING转换到TERMINATED状态。

构造器中的参数

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
       long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
       ThreadFactory threadFactory, RejectedExecutionHandler handler)

corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。当线程池中活跃线程的数量小于corePoolSize时,会直接启动一个新的线程来处理任务,而不管线程池中是否有空闲线程。

maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。由上面corePoolSize属性可知,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中,如果缓存队列满了之后,再有任务到来时,就会再创建线程,直到线程数量达到maximumPoolSize的值。如果此时还有任务到来,就会使用拒绝处理任务的策略RejectedExecutionHandler,可能会抛出任务拒绝异常。

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天

TimeUnit.HOURS;             //小时

TimeUnit.MINUTES;           //分钟

TimeUnit.SECONDS;           //秒

TimeUnit.MILLISECONDS;      //毫秒

TimeUnit.MICROSECONDS;      //微妙

TimeUnit.NANOSECONDS;       //纳秒

workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。

ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和SynchronousQueue。线程池的排队策略与BlockingQueue有关。

threadFactory:线程工厂,主要用来创建线程。

handler:表示拒绝处理任务时的策略,有以下四种取值:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,即创建线程池调用execute或submit方法提交任务的线程处理任务,一般是主线程。

2、深入剖析线程池实现原理

ThreadPoolExecutor继承了AbstractExecutorService类,AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法,ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutdown等。Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable command),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的。

public class ThreadPoolExecutor extends AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService
public interface ExecutorService extends Executor
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);
}

抽象类AbstractExecutorService中主要是submit方法,用于提交任务

//抽象类AbstractExecutorService中主要是submit方法,用于提交任务
public abstract class AbstractExecutorService implements ExecutorService {
	//省略了其他方法
	public Future<?> submit(Runnable task) {
		if (task == null) throw new NullPointerException();
		RunnableFuture<Void> ftask = newTaskFor(task, null);
		execute(ftask);
		return ftask;
	}
	
	public <T> Future<T> submit(Runnable task, T result) {
		if (task == null) throw new NullPointerException();
		RunnableFuture<T> ftask = newTaskFor(task, result);
		execute(ftask);
		return ftask;
	}
	
	public <T> Future<T> submit(Callable<T> task) {
		if (task == null) throw new NullPointerException();
		RunnableFuture<T> ftask = newTaskFor(task);
		execute(ftask);
		return ftask;
	}
}

ThreadPoolExecutor类中的execute方法用于提交任务

public class ThreadPoolExecutor extends AbstractExecutorService {
	//省略其他方法和属性
	public void execute(Runnable command) {
		//若提交的任务command为null,则抛出空指针异常
		if (command == null)
			throw new NullPointerException();
		//workerCountOf(c)会获取线程池中当前正在活动的线程worker数量
		int c = ctl.get();
		if (workerCountOf(c) < corePoolSize) {
			//如果活动线程数小于corePoolSize,就创建一个核心线程worker执行该任务
			if (addWorker(command, true))
				return;
			c = ctl.get(); //在addWorker方法中修改了线程池的状态和线程数量
		}
		//isRunning(c)是判断线程池是否处于运行状态,若处于运行状态,则将任务加到队列中
		if (isRunning(c) && workQueue.offer(command)) {
			//如果添加到队列成功了,会再检查一次线程池的状态。若线程池处于关闭状态,就将刚才添加的任务从队列中移除,并执行拒绝策略。
			int recheck = ctl.get();
			if (! isRunning(recheck) && remove(command))
				reject(command);  //执行拒绝策略
			//如果线程池的状态为SHUTDOWN,则不接受新任务,但会处理队列中的任务
			else if (workerCountOf(recheck) == 0)
				addWorker(null, false);
		}
		//如果上面通过offer方法将任务加入队列失败,就创建普通线程worker来执行任务
		else if (!addWorker(command, false))
			reject(command);  //如果创建Worker线程失败,就执行拒绝策略
	}
}

在ThreadPoolExecutor类中,最核心的提交任务的方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可。

1、判断提交的任务command是否为null,若是null,则抛出空指针异常。

2、AtomicInteger类中有int类型的value属性,该值的高三位表示线程池运行状态,低29位表示线程池中当前活动的线程数量。通过AtomicInteger的get方法可以获取到value的值,即此处的int c = ctl.get()。AtomicInteger类中get方法:public final int get() { return value; }
3、调用workerCountOf(c)方法,得到线程池中当前活动的线程数量。
private static int workerCountOf(int c)  { return c & CAPACITY; }
由上面分析可知,CAPACITY的高三位是0,低29位全是1,所以,(c&CAPACITY)的结果就是c的低29位。因此,将AtomicInteger类中的value属性作为参数传递给c的话,就可以得到线程池中当前活动的线程数量。

4、如果当前活动的线程数量小于核心池大小corePoolSize的值,就调用addWorker方法,该方法的作用是判断线程是否创建并启动成功,若成功,则返回true,否则返回false。如下面的源码一所示。由于Worker实现了Runnable接口,若新建的Worker线程被启动成功,则在该线程中执行提交的任务,并从任务队列中循环获取任务进行执行(如果任务队列中有任务的话)。如下面的源码二所示。所以,在addWorker方法中,创建线程,并通过该线程执行任务。

5、isRunning方法是判断线程池是否在运行状态,如果在运行状态,则通过队列的offer方法将任务添加到任务队列中。如果添加到队列成功了,会再检查一次线程池的状态。若线程池处于关闭状态,就将刚才添加的任务从队列中移除。

private static boolean isRunning(int c) {

    return c < SHUTDOWN;

}

6、通过if (workerCountOf(recheck)==0)判断线程池的状态是否为SHUTDOWN,如果线程池的状态为SHUTDOWN,则不接受新任务,但会处理队列中的任务。

7、如果上面通过队列的offer方法将任务加入队列失败,就尝试直接创建Worker线程来执行任务。如果创建Worker线程失败,就执行拒绝策略。

2.1、源码一:addWorker方法

该方法的作用是:若创建并开启线程成功,则返回true,否则返回false。

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {  //使用自旋+cas失败重试来保证线程竞争问题
        int c = ctl.get();
        int rs = runStateOf(c); //获取线程池的状态
        // 如果线程池是关闭的,或者workQueue队列非空,就直接返回false,不做任何处理
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c); //获取线程池中当前活动的线程数量
			//若线程数量已达到上限,或达到corePoolSize或maximumPoolSize,就拒绝创建线程
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
			//若线程数量未超过上限,就尝试修改ctl的value值,使其加1。修改了线程数量,
            //也有可能修改了线程池的运行状态。这里用了cas操作,如果失败了,下一个循环会继续重试,    
            //直到设置成功。如果设置成功了,就跳出外层for循环,执行下面创建线程的代码
            if (compareAndIncrementWorkerCount(c))
                break retry;
			//重读一次ctl,判断如果线程池的状态改变了,会再重新执行外循环
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
		//创建一个线程worker,将提交上来的任务firstTask交给worker。下面若开启该线程成功,
        //就会执行该线程的run方法,并进一步调用ThreadPoolExecutor类的runWorker方法
        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());
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
					//如果worker线程已经启动了,会抛出异常
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
					//添加新建的worker到线程池中
                    workers.add(w);
                    int s = workers.size();
					//更新历史线程数量的最大值
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true; //设置新增标志位
                }
            } finally {
                mainLock.unlock();
            }
			//如果worker是新增的,就启动该线程,并设置启动成功标志位
            if (workerAdded) {
				//注意:启动线程是调用start方法,下面执行任务是调用run方法
                t.start();
                workerStarted = true;
            }
        }
    } finally {
		//如果启动失败,就从线程池中将该线程移除,并将线程数量减1
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

在上面的addWorker方法中调用了compareAndIncrementWorkerCount方法:

private boolean compareAndIncrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect + 1);
}

在上面的addWorker方法中调用了addWorkerFailed方法:该方法的作用是从线程池中移除线程,并将线程数量减1。

private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

在上面的addWorkerFailed方法中调用了decrementWorkerCount方法:

private void decrementWorkerCount() {
    do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}

private boolean compareAndDecrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect - 1);
}

2.2、源码二:Worker类

Worker是ThreadPoolExecutor内部定义的一个内部类。它实现了Runnable接口,所以可以拿来当线程用。同时它还继承了AbstractQueuedSynchronizer同步器类,主要用来实现一个不可重入的锁。

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    private static final long serialVersionUID = 6138294804551838833L;
    //运行的线程,前面addWorker方法中就是直接通过启动这个线程来启动这个worker
    final Thread thread;
    /** Initial task to run.  Possibly null. */
	//当一个worker刚创建的时候,就先尝试执行这个任务
    Runnable firstTask;
    //记录完成任务的数量
    volatile long completedTasks;
    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
		//通过Worker对象创建一个thread,当thread启动的时候,就是执行worker的run方法
        this.thread = getThreadFactory().newThread(this);
    }
    /** Delegates main run loop to outer runWorker  */
    public void run() {
		//调用ThreadPoolExecutor的runWorker方法
        runWorker(this);
    }
	//省略了其他方法
}

在Worker类的run方法中调用了ThreadPoolExecutor类的runWorker方法,该方法的作用就是执行任务。如果任务队列中有任务,就获取任务并执行。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
	//执行unlock方法,允许其他线程来中断自己
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
		//如果前面的firstTask有值,就直接执行这个任务,如果没有具体的任务,
		//就执行getTask()方法从队列中获取任务,这里会不断执行循环体,
		//除非线程中断或者getTask()返回null才会跳出这个循环
        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
			//判断线程池状态,如果线程池被强制关闭了,就终止当前线程
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
				//执行任务前调用。预留的方法,可扩展
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
					//注意:执行任务只需调用run方法,不是调用start方法
                    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 {
        processWorkerExit(w, completedAbruptly);
    }
}

在上面的runWorker方法中调用了getTask方法,该方法的作用就是从任务队列中获取任务,如果获取成功,则返回队列中的第一个任务,否则返回null。

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

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
		//如果线程池已经关闭了,就直接返回null,如果这里返回null,
		//调用getTask方法处的代码就会跳出while循环,然后执行销毁线程的方法。
		//SHUTDOWN状态表示执行了shutdown()方法,STOP表示执行了shutdownNow()方法
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        //获取线程池中当前活动的线程数量
        int wc = workerCountOf(c);
		//如果设置了核心worker也会超时或者当前活动的worker数量超过了corePoolSize,
		//就要根据时间判断是否要销毁线程了,其实就是从队列获取任务的时候要不要设置超时时间,
		//如果超过这个时间,队列还没有任务进来,就会返回null
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
		//如果上一次循环从任务队列获取到的为null,这时候timedOut就会为true了
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
			//通过CAS来设置WorkerCount,如果多个线程竞争,只有一个可以设置成功。
			//最后如果没设置成功,就进入下一次循环,说不定下一次worker的数量
			//就没有超过corePoolSize了,也就不用销毁worker了
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
			//如果为核心线程设置了存活时间,就通过poll方法在指定时间内从任务队列中获取任务,
			//若在指定时间内获取到了任务(即队列的头元素),则将任务赋值给r变量,没有获取到任务,
			//则返回null给r变量。若没有设置超时时间,就通过take方法从任务队列中获取任务,
			//若任务队列中暂时没有任务,就一直等待有任务。
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;  //如果任务r为null,就设置timedOut为true
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

在上面的runWorker方法中调用了processWorkerExit方法,该方法的作用是:清除和记录垂死的工作线程。如果由于用户任务异常,或活动线程数量少于corePoolSize,或队列不为空但没有活动的线程,该方法就会从线程池中移除线程,并且可能终止线程池或替换工作线程。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

2.3、任务提交后的流程图分析

用户通过execute或submit提交一个任务时,线程池会执行如下流程:

1. 判断线程池中当前运行的worker数量是否超过corePoolSize,如果不超过corePoolSize,就创建一个worker直接执行该任务。—— 线程池最开始是没有worker在运行的。

2. 如果正在运行的worker数量超过或者等于corePoolSize,那么就将该任务加入到workQueue队列中去。

3. 如果workQueue队列满了,也就是offer方法返回false的话,就检查当前运行的worker数量是否小于maximumPoolSize,如果小于就创建一个worker直接执行该任务。

4. 如果当前运行的worker数量大于等于maximumPoolSize,就执行RejectedExecutionHandler策略处理这个任务的提交。

3、使用示例

public class Test {
    public static void main(String[] args) {
		//创建一个线程池,corePoolSize为5,maximumPoolSize为10,
		//keepAliveTime为200ms,任务队列长度为5。或者核心线程数等于CPU核数
		// int corePoolSize = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200,
		 TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
         
        for(int i=0;i<15;i++){
           MyTask myTask = new MyTask(i);
           executor.execute(myTask);
           System.out.println("线程池中线程数目:"+executor.getPoolSize() +
		 	",队列中等待执行的任务数目:"+ executor.getQueue().size() +
		 	",已执行完的任务数目:" + executor.getCompletedTaskCount());
        }
		//关闭线程池
        executor.shutdown();
    }
}
 
class MyTask implements Runnable {
    private int taskNum;
     
    public MyTask(int num) {
        this.taskNum = num;
    }
     
    @Override
    public void run() {
        System.out.println("正在执行task "+taskNum);
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"执行完毕");
    }
}

执行结果:

正在执行task 0
线程池中线程数目:1,队列中等待执行的任务数目:0,已执行完的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完的任务数目:0
正在执行task 1
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行完的任务数目:0
正在执行task 2
线程池中线程数目:4,队列中等待执行的任务数目:0,已执行完的任务数目:0
正在执行task 3
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完的任务数目:0
正在执行task 4
线程池中线程数目:5,队列中等待执行的任务数目:1,已执行完的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:2,已执行完的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:3,已执行完的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:4,已执行完的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行完的任务数目:0
线程池中线程数目:6,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 10
线程池中线程数目:7,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 11
线程池中线程数目:8,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 12
线程池中线程数目:9,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 13
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 14
task 3执行完毕
task 0执行完毕
task 2执行完毕
task 1执行完毕
正在执行task 8
正在执行task 7
正在执行task 6
正在执行task 5
task 4执行完毕
task 10执行完毕
task 11执行完毕
task 13执行完毕
task 12执行完毕
正在执行task 9
task 14执行完毕
task 8执行完毕
task 5执行完毕
task 7执行完毕
task 6执行完毕
task 9执行完毕

从执行结果可以看出,当线程池中线程的数目大于5时(即大于corePoolSize),便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。由于此例中maximumPoolSize的值是10(最多可以创建10个线程来执行任务),任务队列长度是5,任务数是15,任务队列中可以存储5个,所以刚好创建10个线程来执行剩下的10个任务。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。

上面是通过ThreadPoolExecutor直接创建线程池的,另外,也可以使用Executors类中提供的几个静态方法来创建线程池:

Executors.newCachedThreadPool();  //创建容量大小为Integer.MAX_VALUE的线程池

Executors.newSingleThreadExecutor();   //创建容量为1的线程池

Executors.newFixedThreadPool(int);    //创建固定容量大小的线程池

下面是这三个静态方法的具体实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
           new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,
           0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
           new SynchronousQueue<Runnable>());
}

从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。

为什么阿里巴巴要禁用 Executors 创建线程池

 

1、Executors.newSingleThreadExecutor()方法创建的是单线程线程池,只有一个核心线程,线程池最大数量也是1,也就是说线程池只能创建一个线程,那就是核心线程。使用的阻塞队列是LinkedBlockingQueue,其长度为Integer.MAX_VALUE,可以认为是无界队列,因此往队列中可以插入无限多的任务,在资源有限的时候容易引起OOM异常。另外因为无界队列,maximumPoolSize和keepAliveTime参数将无效,压根就不会创建非核心线程。

2、Executors.newFixedThreadPool(int nThreads)方法创建的是固定核心线程的线程池,固定核心线程数由用户传入。核心线程数和最大线程数都是nThreads参数。使用的阻塞队列也是LinkedBlockingQueue,可以往队列中插入无限多的任务,在资源有限的时候容易引起OOM异常。

3、Executors.newCachedThreadPool()方法可以根据需要创建任意数量的线程池。核心线程数为0,即没有核心线程。maximumPoolSize的值是Integer.MAX_VALUE,可以认为可以无限创建线程。使用的阻塞队列是SynchronousQueue,是一个不存储元素的队列,可以理解为队列永远是满的,因此最终会创建非核心线程来执行任务。对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常。

4、Executors.newScheduledThreadPool :核心线程固定,大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

创建一个周期性执行任务的线程池。如果闲置,非核心线程会在DEFAULT_KEEPALIVEMILLIS时间内被回收。

public static ScheduledExecutorService newScheduledThreadPool (int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor( int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
        new DelayedWorkQueue());
}

ScheduledThreadPool可以创建的最大线程数是Integer.MAX_VALUE,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常。

因此,最好是根据需要自己调用ThreadPoolExecutor构造函数传参创建合适的线程池。

ThreadPoolExecutor中使用ThreadFactory创建线程

package com.db.conn.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class DsConnThreadUtil {

    private static final Logger log = LoggerFactory.getLogger(DsConnThreadUtil.class);

    private static final int CORE_POOL_SIZE = 10;

    private static final int MAX_POOL_SIZE = 100;

    private static final int KEEP_ALIVE_TIME = 1;

    private static final ThreadPoolExecutor THREAD_EXECUTOR;

    static {
        //通过ThreadFactory创建线程,给每个线程设置名称
        ThreadFactory threadFactory = new ThreadFactory() {
            AtomicInteger atomicInteger = new AtomicInteger();
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"Thread_" + atomicInteger.getAndIncrement());
            }
        };
        ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(MAX_POOL_SIZE);
        THREAD_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,
            KEEP_ALIVE_TIME,TimeUnit.SECONDS,blockingQueue,threadFactory);
    }

    public static void saveOrUpdate(String dsId, String sql, Object... params){
        Runnable t = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                DsConnUtil.saveOrUpdate(dsId,sql,params);
                log.info("子线程新增或修改数据结束,当前线程名称=====" 
                + Thread.currentThread().getName());
            }
        };
        THREAD_EXECUTOR.execute(t);
        System.out.println("线程池中线程数目:" + THREAD_EXECUTOR.getPoolSize() +
                ",队列中等待执行的任务数目:" + THREAD_EXECUTOR.getQueue().size() +
                ",已执行完的任务数目:" + THREAD_EXECUTOR.getCompletedTaskCount());
        log.info("新增或修改完成,主线程名称=====" + Thread.currentThread().getName());
    }

    public static void batchSaveOrUpdate(String dsId, String sql, List<Object[]> params){
        Runnable t = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                DsConnUtil.batchSaveOrUpdate(dsId,sql,params);
                log.info("子线程批量新增数据结束,当前线程名称=====" + Thread.currentThread().getName());
            }
        };
        THREAD_EXECUTOR.execute(t);
        System.out.println("线程池中线程数目:" + THREAD_EXECUTOR.getPoolSize() +
                ",队列中等待执行的任务数目:" + THREAD_EXECUTOR.getQueue().size() +
                ",已执行完的任务数目:" + THREAD_EXECUTOR.getCompletedTaskCount());
        log.info("批量新增或修改完成,主线程名称=====" + Thread.currentThread().getName());
    }

    public static List<Map<String,Object>> queryForList(String dsId, String sql, Object... params){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程池中线程数目:" + THREAD_EXECUTOR.getPoolSize() +
                ",队列中等待执行的任务数目:" + THREAD_EXECUTOR.getQueue().size() +
                ",已执行完的任务数目:" + THREAD_EXECUTOR.getCompletedTaskCount());
        return DsConnUtil.queryForList(dsId,sql,params);
    }

}

4、如何合理配置线程池的大小

一般需要根据任务的类型来配置线程池大小:

如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1。

如果是IO密集型任务,参考值可以设置为2*NCPU。(NCPU表示CPU核数)

当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

什么是CPU密集型、IO密集型?

CPU密集型(CPU-bound

CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能与CPU相比,不会差得很多,CPU在很短的时间内就可以完成从内存或硬盘中读写数据,即I/O在很短的时间就可以完成,而绝大部分时间都花在CPU处理运算的过程中,CPU Loading很高。

在多重程序系统中,大部分时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,便是属于CPU bound的程序。

CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现,因此屏蔽掉了等待I/O的时间。

IO密集型(I/O bound

IO密集型指的是系统的硬盘、内存性能与CPU相比,要差很多,此时,系统运作,大部分时间花在CPU等待从内存或硬盘中读写数据,即I/O操作需要花费很长时间,此时CPU Loading并不高。

I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。

CPU密集型  vs  IO密集型

我们可以把任务分为计算密集型和IO密集型。

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPUCPU密集型任务同时进行的数量应当等于CPU的核心数

计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。

IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

总之,计算密集型程序适合C语言多线程,I/O密集型适合脚本语言开发的多线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值