线程池 —— 认识ThreadPoolExecuotr

线程池 —— 认识ThreadPoolExecuotr

ThreadPoolExecuotr 构造函数源码(JDK 1.8)

   /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    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;
    }

参数含义

  • corePoolSize:
    初始线程数,除非设置了allowCoreThreadTimeOut(在源码中处理时会体现的变量).
  • maximumPoolSize:
    线程池中最大的线程数量,超过最大的线程数,后续提交的任务都会被RejectedExecutionHandler拒绝。
  • keepAliveTime
    调用线程池会增加线程,就算初始线程还有值,当线程超过corePoolSize时,会进入Queue中,当线程总数超过maximumPoolSize时,超过一定时间(keepAliveTime)会执行RejectedExecutionHandler(饱和策略)
  • workQueue
    等待队列(如果线程池中的线程数量大于或者等于corePoolSize的时候,把该任务封装成一个work对象,放入到等待队列中,因为队列种类很多,使用不同队列就会执行不同排队机制),队列是要通过execute方法提交的runnable任务.
  • threadFactory:
    创建新线程使用的工厂
  • unit
    keepAliveTime的单位
  • handler
    线程池饱和策略(阻塞队列满了,并且没有空闲的线程,这时,如果继续提交任务,就需要采用一种策略处理该任务,线程池提供五种策略:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy、自定义策略(通过实现RejectedExecutionHandler接口))


执行线程

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    // 当线程小于核心线程时
    if (workerCountOf(c) < corePoolSize) {
    	// 创建线程(创建工作线程执行任务)
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 当前线程数目大于核心线程数目(corePoolSize),并且wokeQueue任务队列没满,将任务放到队列里面
    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);
}

线程与线程池执行

线程处理流程与线程池执行示意图

  • 1)如果当前运行的线程少于corePoolSize,则创新线程来执行任务(注意,执行这一步骤需要获取全局锁)
  • 2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue
  • 3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)
  • 4)如果创建新线程将是当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法(饱和策略)


BlockingQueue workQueue 队列

分类

常用队列主要有以下两种:(通过不同的实现方式,还可以延伸出很多不同类型的队列,DelayQueue就是其中的一种)

  • 先进先出(FIFO)
    先插入的队列的元素也最先出队列,类似于排队的功能。某种意义说,这种队列体现了“公平性”,比如:ReentrantLock
  • 后进先出(LIFO)
    后插入队列的元素最先出队列,这种队列优先处理最近发生的事件。

核心方法介绍

思考:多线程环境下为什么需要BlockingQueue?

使用BlockingQueue可以 使开发者不用关心什么时候需要阻塞线程,什么时候唤醒线程,使用BlockingQueuew制定规则,令它可以自动将这些问题解决。下面介绍一些常用的BlockingQueue的实现。

放入数据
  • offer(anObject)
    将anObject放入BlockingQuque,即如果BlockingQueue可以容纳,则返回true,否则返回false.(不阻塞当前执行方法的线程)
  • offer(E o, long timeout, TimeUnit unit)
    设置等待时间,如果在等待时间内,无法向队列插入o,则返回失败
  • put(anObject)
    将anObject放入BlockingQueue,如果BlockingQueue没有空间,则调用此方法的线程被阻塞,直到BlockingQueue里面有空间再进行放入anObject。3
获取数据
  • poll(long timeout, TimeUnit unit)
    在规定时间内从BlockingQueue队首中取出对象,如果在指定时间内没有取出,则返回失败。
  • take()
    取走BlockingQueue队首对象,若BlockingQueue为空,让当前线程池等待(不允许有新的线程进入队列),直到获取到要的对象
  • drainTo()
    一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁

常用队列

以下五种阻塞队列都是BlockingQueue接口的实现

ArrayBlockingQueue 数组阻塞队列
  • 基于数组的阻塞队列实现
  • 内部维护一个定长数组,用于缓存队列中的数据对象,是一个较为常用的阻塞队列
  • ArrayBlockingQueue内部还保存着两个整形变量,分别表示队列的头部和尾部在数组中的位置
  • 生产者放入数据,消费者获取数据,都是共用同一个锁对象,因此两者不能真正并行运行,这点 与LinkedBlockingQueue截然不同
  • ArrayBlockingQueue默认采用非公平锁,可以手动控制对象的内部锁为公平锁
  • ArrayBlockingQueue和LinkedBlockingQueue明显区别
    • ArrayBlockingQueue在插入或删除元素时不会产生或销毁任何额外的对象实例
    • LinkedBlockingQueue会生成一个额外的Node对象
LinkedBlockingQueue 链表阻塞队列
  • 基于链表的阻塞队列实现
  • 与ArrayBlockingQueue类似,内部同样维护一个数据缓冲队列(该队列由一个链表构成)
  • 当生产者往队列放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回
  • 只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数设置该值),才会阻塞生产队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,同理,消费者端处理也是基于同样原理。
  • LinkedBlockingQueue之所以能够高效的处理并发数据,因为生产者端和消费者端分别采用了独立的锁来控制数据同步,支持高并发场景下生产者与消费者并行操作队列中的数据
  • 如果不指定LinkedBlockingQueue大小,默认是无限大小的容量(Integer.MAX_VALUE),如果生产者速度大于消费者速度,可以导致内存不够用的情况

注:Executors.newFixedThreadPool(int) 的实现是LinkedBlockingQueue;
注:Executors.newSingleThreadExecutor() 的实现是LinkedBlockingQueue;

DelayQueue 延时队列
  • DelayQueue中的元素只有在指定的延迟时间结束后,才能够从队列中获取到元素
  • DelayQueue 是一个没有大小限制的队列
  • 插入数据操作(生产者)永远不会被阻塞,而获取数据操作(消费者)才会被阻塞
PriorityBlockingQueue 优先级队列

基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定)

  • PriorityBlockingQueue 不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者
  • 所以在使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。
SynchronousQueue 同步队列

一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。

两种声明方式
  • 公平模式
    SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体现整体的公平策略;
  • 非公平模式(默认)
    SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则 很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。

注:Executors.newCachedThreadPool();的实现是SynchronousQueue;

性能测试

参考:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue性能测试(转)

package com.java.offer.frank.thread.action.demo;

import java.util.concurrent.*;

/**
 * 测试ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue性能
 */
public class BlockingQueueTest {

    private static int THREAD_NUM;
    private static int N = 1000000;
    private static ExecutorService executor;
    public static void main(String[] args) throws Exception {
        System.out.println("Producer\tConsumer\tcapacity \t LinkedBlockingQueue \t ArrayBlockingQueue \t SynchronousQueue");

        for(int j = 0; j<10; j++){
            THREAD_NUM = (int) Math.pow(2, j);
            executor = Executors.newFixedThreadPool(THREAD_NUM * 2);

            for (int i = 0; i < 10; i++) {
                int length = (i == 0) ? 1 : i * 10;
                System.out.print(THREAD_NUM + "\t\t");
                System.out.print(THREAD_NUM + "\t\t");
                System.out.print(length + "\t\t");
                System.out.print(doTest2(new LinkedBlockingQueue<Integer>(length), N) + "/s\t\t\t");
                System.out.print(doTest2(new ArrayBlockingQueue<Integer>(length), N) + "/s\t\t\t");
                System.out.print(doTest2(new SynchronousQueue<Integer>(), N) + "/s");
                System.out.println();
            }

            executor.shutdown();
        }
    }

    private static class Producer implements Runnable{
        int n;
        BlockingQueue<Integer> q;

        public Producer(int initN, BlockingQueue<Integer> initQ){
            n = initN;
            q = initQ;
        }

        public void run() {
            for (int i = 0; i < n; i++)
                try {
                    q.put(i);
                } catch (InterruptedException ex) {
                }
        }
    }

    private static class Consumer implements Callable<Long>{
        int n;
        BlockingQueue<Integer> q;

        public Consumer(int initN, BlockingQueue<Integer> initQ){
            n = initN;
            q = initQ;
        }

        public Long call() {
            long sum = 0;
            for (int i = 0; i < n; i++)
                try {
                    sum += q.take();
                } catch (InterruptedException ex) {
                }
            return sum;
        }
    }

    private static long doTest2(final BlockingQueue<Integer> q, final int n)
            throws Exception {
        CompletionService<Long> completionServ = new ExecutorCompletionService<Long>(executor);

        long t = System.nanoTime();
        for(int i=0; i<THREAD_NUM; i++){
            executor.submit(new Producer(n/THREAD_NUM, q));
        }
        for(int i=0; i<THREAD_NUM; i++){
            completionServ.submit(new Consumer(n/THREAD_NUM, q));
        }

        for(int i=0; i<THREAD_NUM; i++){
            completionServ.take().get();
        }

        t = System.nanoTime() - t;
        return (long) (1000000000.0 * N / t); // Throughput, items/sec
    }
}

  • 执行结果
    配置:WIN10
    在这里插入图片描述
...
...
Connected to the target VM, address: '127.0.0.1:55238', transport: 'socket'
Producer	Consumer	capacity 	 LinkedBlockingQueue 	 ArrayBlockingQueue 	 SynchronousQueue
1		1		1		91965/s			91959/s			366617/s
1		1		10		953696/s			943459/s			277284/s
1		1		20		1904141/s			1926988/s			265989/s
1		1		30		2968361/s			2916859/s			255032/s
1		1		40		4548421/s			3855967/s			222687/s
1		1		50		9430599/s			4767307/s			241964/s
1		1		60		7318153/s			5609283/s			238902/s
1		1		70		7439673/s			6738054/s			247625/s
1		1		80		5553072/s			7697482/s			279672/s
1		1		90		4524037/s			9025759/s			252031/s
2		2		1		90973/s			91514/s			954934/s
2		2		10		713678/s			527480/s			985417/s
2		2		20		1443258/s			1119546/s			1036098/s
2		2		30		1983797/s			1806370/s			1195615/s
2		2		40		2964555/s			2051601/s			985493/s
2		2		50		3795900/s			2762851/s			919701/s
2		2		60		4799079/s			3248316/s			903594/s
2		2		70		5364073/s			3854182/s			987358/s
2		2		80		6235140/s			4401578/s			1257740/s
2		2		90		6661896/s			5160448/s			1113124/s
4		4		1		92107/s			90642/s			2689548/s
4		4		10		1726540/s			358131/s			2679777/s
4		4		20		3353265/s			1123323/s			2720432/s
4		4		30		4925678/s			1567281/s			2727719/s
4		4		40		5911964/s			2110198/s			2724256/s
4		4		50		6628860/s			2467913/s			2750098/s
4		4		60		6921215/s			2164982/s			2719990/s
4		4		70		6805804/s			4662687/s			2701881/s
4		4		80		7344165/s			3536709/s			2708836/s
4		4		90		7027673/s			3130522/s			2660962/s
8		8		1		93545/s			92869/s			2201049/s
8		8		10		1733532/s			463352/s			2215217/s
8		8		20		3115509/s			703680/s			2225547/s
8		8		30		4586333/s			1515047/s			2297381/s
8		8		40		5449321/s			1602711/s			2320047/s
8		8		50		6510586/s			1663456/s			2247328/s
8		8		60		6781858/s			2632964/s			2272160/s
8		8		70		6758889/s			3239661/s			2158761/s
8		8		80		7336902/s			3989830/s			2200392/s
8		8		90		7487888/s			3542118/s			2275863/s
16		16		1		97642/s			90259/s			2140706/s
16		16		10		1643182/s			341132/s			2313325/s
16		16		20		3142675/s			754738/s			2142465/s
16		16		30		4761555/s			1351709/s			2056255/s
16		16		40		5872804/s			2062390/s			2221156/s
16		16		50		6232164/s			2345968/s			2177814/s
16		16		60		6875158/s			2773055/s			2112430/s
16		16		70		6949676/s			3249319/s			2239116/s
16		16		80		7173323/s			3919831/s			2077181/s
16		16		90		7478145/s			4756161/s			2073870/s
32		32		1		110686/s			91912/s			2088903/s
32		32		10		1694806/s			279348/s			2032431/s
32		32		20		3290346/s			865011/s			2016924/s
32		32		30		4795427/s			1834324/s			2049761/s
32		32		40		5911143/s			2177243/s			2177071/s
32		32		50		6106941/s			2461343/s			2082106/s
32		32		60		6825300/s			3015013/s			2045709/s
32		32		70		6746067/s			3066112/s			2196008/s
32		32		80		6985215/s			3824910/s			2190782/s
32		32		90		7457716/s			3463629/s			2164759/s
64		64		1		207880/s			91256/s			2087170/s
64		64		10		1683912/s			389424/s			2079147/s
64		64		20		3238991/s			733675/s			2118551/s
64		64		30		4378511/s			1336209/s			2234310/s
64		64		40		5519208/s			1547645/s			2194217/s
64		64		50		5888789/s			2605918/s			2109774/s
64		64		60		6709076/s			2682557/s			2096178/s
64		64		70		6655879/s			3277397/s			2071702/s
64		64		80		7081445/s			3737165/s			2115328/s
64		64		90		7126115/s			3994240/s			2091770/s
128		128		1		204554/s			87075/s			2176845/s
128		128		10		1619171/s			328204/s			2181884/s
128		128		20		3092636/s			533778/s			2066740/s
128		128		30		4360306/s			1017100/s			2141616/s
128		128		40		4991422/s			1773710/s			2060649/s
128		128		50		5833475/s			1846381/s			2063006/s
128		128		60		6001032/s			2065971/s			2079865/s
128		128		70		6661572/s			2430652/s			2028861/s
128		128		80		5994898/s			2799312/s			2048669/s
128		128		90		6704839/s			2725811/s			2031984/s
256		256		1		158258/s			79638/s			2068807/s
256		256		10		1451439/s			285390/s			1967928/s
256		256		20		2775005/s			690559/s			2036268/s
256		256		30		3790749/s			1025905/s			2149660/s
256		256		40		4822570/s			1356318/s			2000817/s
256		256		50		5570580/s			1849989/s			2041918/s
256		256		60		6021020/s			2767182/s			1989533/s
256		256		70		6386752/s			2752471/s			2210044/s
256		256		80		6626615/s			2916560/s			2084160/s
256		256		90		6148756/s			2529608/s			2060077/s
512		512		1		147759/s			75621/s			2082208/s
512		512		10		1351367/s			302206/s			1987115/s
512		512		20		2639394/s			908092/s			2076575/s
512		512		30		3561041/s			1478838/s			2009688/s
512		512		40		4947149/s			2257426/s			2045543/s
512		512		50		5480611/s			1580416/s			2141108/s
512		512		60		5857036/s			2182799/s			1971585/s
512		512		70		6434666/s			1973550/s			2021387/s
512		512		80		6291021/s			1907188/s			2096352/s
512		512		90		6658033/s			3990411/s			2108029/s
Disconnected from the target VM, address: '127.0.0.1:55238', transport: 'socket'
Process finished with exit code 0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章