异步编程学习之路(五)-线程池原理及使用

本文是异步编程学习之路(五)-线程池原理及使用,若要关注前文,请点击传送门:

异步编程学习之路(四)-睡眠、唤醒、让步、合并

前文我们详细介绍了线程之间协同合作的方法,在本文中我们将再进一步详细讲解线程池的原理及使用。

个人认为,如果想要学好线程池就必须先从ThreadPoolExcetor源码开始讲起,我不建议直接使用Executors来创建一个线程池。至于为什么不建议直接使用的原因,阅读完本文大家就能够明白了。

一、ThreadPoolExcetor源码分析

1、ThreadPoolExcetor构造

在Jdk8中ThreadPoolExcetor有四种构造方法,每种构造方法上都有不同的参数,代码如下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
	super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}

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

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
	super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new ThreadPoolExecutor.RejectHandler());
}

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
	super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new ThreadPoolExecutor.RejectHandler());
}

下面将详细讲解这几个参数。

2、corePoolSize(线程池的核心线程数)

当任务过来后判断当前在执行的线程是否达到核心线程数,如果达到则进入缓存队列,否则创建新线程执行任务。

3、maximumPoolSize(线程池最大能容忍的线程数)

当任务过来后判断是否大于核心线程数,如果超过核心线程数再判断是否大于最大线程数,如果大于最大线程数则采取拒绝策略。

4、keepAliveTime(线程存活时间)

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

这里有两个地方可以设置线程存活时间,一个是在初始化ThreadPoolExcetor的时候在构造参数上传入,另一个是调用ThreadPoolExcetor中的设置存活时间的方法,代码如下:

/**
 * Sets the time limit for which threads may remain idle before
 * being terminated.  If there are more than the core number of
 * threads currently in the pool, after waiting this amount of
 * time without processing a task, excess threads will be
 * terminated.  This overrides any value set in the constructor.
 *
 * @param time the time to wait.  A time value of zero will cause
 *        excess threads to terminate immediately after executing tasks.
 * @param unit the time unit of the {@code time} argument
 * @throws IllegalArgumentException if {@code time} less than zero or
 *         if {@code time} is zero and {@code allowsCoreThreadTimeOut}
 * @see #getKeepAliveTime(TimeUnit)
 */
public void setKeepAliveTime(long time, TimeUnit unit) {
	if (time < 0)
		throw new IllegalArgumentException();
	if (time == 0 && allowsCoreThreadTimeOut())
		throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
	long keepAliveTime = unit.toNanos(time);
	long delta = keepAliveTime - this.keepAliveTime;
	this.keepAliveTime = keepAliveTime;
	if (delta < 0)
		interruptIdleWorkers();
}

setKeepAliveTime方法有两个参数,一个是超时时间,一个是时间单位。大家注意一下这里有一个变量是allowsCoreThreadTimeOut,如果time < 0或者time == 0 && allowsCoreThreadTimeOut()抛出异常,代码如下:

/**
 * Returns true if this pool allows core threads to time out and
 * terminate if no tasks arrive within the keepAlive time, being
 * replaced if needed when new tasks arrive. When true, the same
 * keep-alive policy applying to non-core threads applies also to
 * core threads. When false (the default), core threads are never
 * terminated due to lack of incoming tasks.
 *
 * @return {@code true} if core threads are allowed to time out,
 *         else {@code false}
 *
 * @since 1.6
 */
public boolean allowsCoreThreadTimeOut() {
	return allowCoreThreadTimeOut;
}

如果当前线程池允许核心线程过期或者在keepAlive时间内没有任务到达则allowCoreThreadTimeOut()返回true。

这个方法直接返回了allowCoreThreadTimeOut,这个参数在allowsCoreThreadTimeOut()的重载方法中可以设置,代码如下:

/**
 * Sets the policy governing whether core threads may time out and
 * terminate if no tasks arrive within the keep-alive time, being
 * replaced if needed when new tasks arrive. When false, core
 * threads are never terminated due to lack of incoming
 * tasks. When true, the same keep-alive policy applying to
 * non-core threads applies also to core threads. To avoid
 * continual thread replacement, the keep-alive time must be
 * greater than zero when setting {@code true}. This method
 * should in general be called before the pool is actively used.
 *
 * @param value {@code true} if should time out, else {@code false}
 * @throws IllegalArgumentException if value is {@code true}
 *         and the current keep-alive time is not greater than zero
 *
 * @since 1.6
 */
public void allowCoreThreadTimeOut(boolean value) {
	if (value && keepAliveTime <= 0)
		throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
	if (value != allowCoreThreadTimeOut) {
		allowCoreThreadTimeOut = value;
		if (value)
			interruptIdleWorkers();
	}
}

allowCoreThreadTimeout : 默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

默认配置allowCoreThreadTimeOut为false,核心线程在原则上会一直存在不会被GC回收,如果设置allowCoreThreadTimeOut为true的话,核心线程也会被GC回收掉。具体使用到allowCoreThreadTimeOut的代码如下:

/**
 * 
 * Performs blocking or timed wait for a task, depending on
 * current configuration settings, or returns null if this worker
 * must exit because of any of:
 * 1. There are more than maximumPoolSize workers (due to
 *    a call to setMaximumPoolSize).
 * 2. The pool is stopped.
 * 3. The pool is shutdown and the queue is empty.
 * 4. This worker timed out waiting for a task, and timed-out
 *    workers are subject to termination (that is,
 *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
 *    both before and after the timed wait, and if the queue is
 *    non-empty, this worker is not the last thread in the pool.
 *
 * @return task, or null if the worker must exit, in which case
 *         workerCount is decremented
 */
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?
		boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

		if ((wc > maximumPoolSize || (timed && timedOut))
			&& (wc > 1 || workQueue.isEmpty())) {
			if (compareAndDecrementWorkerCount(c))
				return null;
			continue;
		}

		try {
			Runnable r = timed ?
				workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
				workQueue.take();
			if (r != null)
				return r;
			timedOut = true;
		} catch (InterruptedException retry) {
			timedOut = false;
		}
	}
}

根据当前配置设置执行阻塞或定时等待任务,如果工作线程存在以下原因的话则退出并返回null:

1、工作线程超过线程池最大线程数(因为设置了线程池的最大线程数)。

2、线程池已停止。

3、关闭线程池&&队列为空。

4、该线程在等待任务的时候超时,超时的线程将被终止。(allowCoreThreadTimeOut || workerCount > corePoolSize)

还有一点需要注意的是下面这句代码:

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
    if (compareAndDecrementWorkerCount(c))
				return null;
    continue;
}

也就是说在((当前工作线程数 > 最大线程数) || (当前队列不为空 && (允许核心线程超时 || 当前工作线程数 > 核心线程)) && (当前工作线程数 > 1 || 队列为空) 的时候GC会将线程回收。

5、unit(存活时间单位)

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

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

6、workQueue(任务阻塞队列)

workQueue是用来存储等待执行的任务,一般有三种选择(ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue),他们的初始化参数如下:

/**
 * Creates an {@code ArrayBlockingQueue} with the given (fixed)
 * capacity and default access policy.
 *
 * @param capacity the capacity of this queue
 * @throws IllegalArgumentException if {@code capacity < 1}
 */
public ArrayBlockingQueue(int capacity) {
	this(capacity, false);
}

/**
 * Creates an {@code ArrayBlockingQueue} with the given (fixed)
 * capacity and the specified access policy.
 *
 * @param capacity the capacity of this queue
 * @param fair if {@code true} then queue accesses for threads blocked
 *        on insertion or removal, are processed in FIFO order;
 *        if {@code false} the access order is unspecified.
 * @throws IllegalArgumentException if {@code capacity < 1}
 */
public ArrayBlockingQueue(int capacity, boolean fair) {
	if (capacity <= 0)
		throw new IllegalArgumentException();
	this.items = new Object[capacity];
	lock = new ReentrantLock(fair);
	notEmpty = lock.newCondition();
	notFull =  lock.newCondition();
}

/**
 * Creates an {@code ArrayBlockingQueue} with the given (fixed)
 * capacity, the specified access policy and initially containing the
 * elements of the given collection,
 * added in traversal order of the collection's iterator.
 *
 * @param capacity the capacity of this queue
 * @param fair if {@code true} then queue accesses for threads blocked
 *        on insertion or removal, are processed in FIFO order;
 *        if {@code false} the access order is unspecified.
 * @param c the collection of elements to initially contain
 * @throws IllegalArgumentException if {@code capacity} is less than
 *         {@code c.size()}, or less than 1.
 * @throws NullPointerException if the specified collection or any
 *         of its elements are null
 */
public ArrayBlockingQueue(int capacity, boolean fair,
						  Collection<? extends E> c) {
	this(capacity, fair);

	final ReentrantLock lock = this.lock;
	lock.lock(); // Lock only for visibility, not mutual exclusion
	try {
		int i = 0;
		try {
			for (E e : c) {
				checkNotNull(e);
				items[i++] = e;
			}
		} catch (ArrayIndexOutOfBoundsException ex) {
			throw new IllegalArgumentException();
		}
		count = i;
		putIndex = (i == capacity) ? 0 : i;
	} finally {
		lock.unlock();
	}
}

 ArrayBlockingQueue数据结构是数组,存在三种构造方式,我们通常用第一种构造方式,参数含义分别是:

1、capacity:数组长度。

2、fair:默认false,如果设置为true则插入或删除时阻塞的线程的队列访问按FIFO顺序处理,如果设置为false则访问顺序未知。

3、c:最初要包含的元素的集合,下面的items是Object数组。

/**
 * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
 *
 * @param capacity the capacity of this queue
 * @throws IllegalArgumentException if {@code capacity} is not greater
 *         than zero
 */
public LinkedBlockingQueue(int capacity) {
	if (capacity <= 0) throw new IllegalArgumentException();
	this.capacity = capacity;
	last = head = new Node<E>(null);
}

 LinkedBlockingQueue数据结构是链表,只有一种构造方式,参数是链表长度。

/**
 * Creates a {@code SynchronousQueue} with nonfair access policy.
 */
public SynchronousQueue() {
	this(false);
}

/**
 * Creates a {@code SynchronousQueue} with the specified fairness policy.
 *
 * @param fair if true, waiting threads contend in FIFO order for
 *        access; otherwise the order is unspecified.
 */
public SynchronousQueue(boolean fair) {
	transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}

SynchronousQueue存在两种构造方式,参数含义:

fair:默认false,如果设置为true则插入或删除时阻塞的线程的队列访问按FIFO顺序处理,如果设置为false则访问顺序未知。

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

7、threadFactory(用来创建线程的工厂)

ThreadFactory很简单,就是一个线程工厂也就是负责生产线程的,代码如下:

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

其中newThread方法就是用来生产线程的,子类需要实现这个方法来根据自己规则生产相应的线程。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}

如果ThreadPoolExecutor在构造时没有传入threadFactory则默认使用Executors.defaultThreadFactory()来作为线程工厂。

8、handler(任务拒绝策略)

任务拒绝策略常用的有四种,最上层接口代码如下:

/**
 * A handler for tasks that cannot be executed by a {@link ThreadPoolExecutor}.
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface RejectedExecutionHandler {

    /**
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     * task.  This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>In the absence of other alternatives, the method may throw
     * an unchecked {@link RejectedExecutionException}, which will be
     * propagated to the caller of {@code execute}.
     *
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

Java中的实现类有四种:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

开发人员可以在初始化线程池的时候根据业务需求灵活配置不同的拒绝策略。

上文中说不建议直接使用Executors,因为Executors在四个方法中(newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor)阻塞队列默认大小是Integer.MAX_VALUE(无限大),这样造成的问题就是超过核心线程数的线程会不断放入阻塞队列中(前提是核心最大线程数设置的不合理),为了规避这个风险强烈建议在初始化ThreadPoolExcetor时自定义阻塞队列的大小。

二、Executors线程池

Executors中常用的线程池有四种,下面将通过实例讲解使用方法,代码如下:

 1、 FixedThreadPool

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  

public class ThreadPoolTest {  
    public static void main(String[] args) {  
        ExecutorService threadPool = Executors.newFixedThreadPool(3);  
        for(int i = 1; i < 5; i++) {  
            final int taskID = i;  
            threadPool.execute(new Runnable() {  
                public void run() {  
                    for(int i = 1; i < 5; i++) {  
                        try {  
                            Thread.sleep(20);// 为了测试出效果,让每次任务执行都需要一定时间  
                        } catch (InterruptedException e) {  
                            e.printStackTrace();  
                        }  
                        System.out.println("第" + taskID + "次任务的第" + i + "次执行");  
                    }  
                }  
            });  
        }  
        threadPool.shutdown();// 任务执行完毕,关闭线程池  
    }  
}  

运行结果:

第1次任务的第1次执行  
第2次任务的第1次执行  
第3次任务的第1次执行  
第2次任务的第2次执行  
第3次任务的第2次执行  
第1次任务的第2次执行  
第3次任务的第3次执行  
第1次任务的第3次执行  
第2次任务的第3次执行  
第3次任务的第4次执行  
第2次任务的第4次执行  
第1次任务的第4次执行  
第4次任务的第1次执行  
第4次任务的第2次执行  
第4次任务的第3次执行  
第4次任务的第4次执行  

  上段代码中,创建了一个固定大小的线程池,容量为3,然后循环执行了4个任务,由输出结果可以看到,前3个任务首先执行完,然后空闲下来的线程去执行第4个任务,在FixedThreadPool中,有一个固定大小的池,如果当前需要执行的任务超过了池大小,那么多于的任务等待状态,直到有空闲下来的线程执行任务,而当执行的任务小于池大小,空闲的线程也不会去销毁。

 2、 CachedThreadPool

 上段代码其它地方不变,将newFixedThreadPool方法换成newCachedThreadPool方法。

运行结果:

第3次任务的第1次执行  
第4次任务的第1次执行  
第1次任务的第1次执行  
第2次任务的第1次执行  
第4次任务的第2次执行  
第3次任务的第2次执行  
第2次任务的第2次执行  
第1次任务的第2次执行  
第2次任务的第3次执行  
第3次任务的第3次执行  
第1次任务的第3次执行  
第4次任务的第3次执行  
第2次任务的第4次执行  
第4次任务的第4次执行  
第3次任务的第4次执行  
第1次任务的第4次执行  

可见,4个任务是交替执行的,CachedThreadPool会创建一个缓存区,将初始化的线程缓存起来,如果线程有可用的,就使用之前创建好的线程,如果没有可用的,就新创建线程,终止并且从缓存中移除已有60秒未被使用的线程。

3、 SingleThreadExecutor        

 上段代码其它地方不变,将newFixedThreadPool方法换成newSingleThreadExecutor方法。     

运行结果: 

第1次任务的第1次执行  
第1次任务的第2次执行  
第1次任务的第3次执行  
第1次任务的第4次执行  
第2次任务的第1次执行  
第2次任务的第2次执行  
第2次任务的第3次执行  
第2次任务的第4次执行  
第3次任务的第1次执行  
第3次任务的第2次执行  
第3次任务的第3次执行  
第3次任务的第4次执行  
第4次任务的第1次执行  
第4次任务的第2次执行  
第4次任务的第3次执行  
第4次任务的第4次执行  

四个任务是顺序执行的,SingleThreadExecutor得到的是一个单个的线程,这个线程会保证你的任务执行完成,如果当前线程意外终止,会创建一个新线程继续执行任务,这和我们直接创建线程不同,也和newFixedThreadPool(1)不同。 

 4、ScheduledThreadPool    

import java.util.concurrent.ScheduledExecutorService;  
import java.util.concurrent.TimeUnit;  

public class ThreadPoolTest {  
    public static void main(String[] args) {  
        ScheduledExecutorService schedulePool = Executors.newScheduledThreadPool(1);  
        // 5秒后执行任务  
        schedulePool.schedule(new Runnable() {  
            public void run() {  
                System.out.println("爆炸");  
            }  
        }, 5, TimeUnit.SECONDS);  
        // 5秒后执行任务,以后每2秒执行一次  
        schedulePool.scheduleAtFixedRate(new Runnable() {  
            @Override  
            public void run() {  
                System.out.println("爆炸");  
            }  
        }, 5, 2, TimeUnit.SECONDS);  
    }  
}  

ScheduledThreadPool可以定时的或延时的执行任务。

本文到此结束,之后的文章就开始学习Feature、Callable等相关使用。

异步编程学习之路(六)-Future和Callable原理及使用(包括FutureTask相关介绍)

发布了352 篇原创文章 · 获赞 390 · 访问量 37万+

猜你喜欢

转载自blog.csdn.net/qq_19734597/article/details/85224754