java的ThreadPoolExecutor与BlockingQueue

来源:互联网 发布:淘宝子账号在哪关闭 编辑:程序博客网 时间:2024/06/05 07:48

本文介绍java中经常用到的ThreadPoolExecutor,并共同探讨学习其中用到的参数BlockingQueue。在介绍具体的实例之前,先来看看ThreadPoolExecutor在hbase中的应用。hbase中的HTable类用于客户端获得服务器上一个table的连接,其构造函数中会创建线程池,后续数据传输的连接都从该线程池中获取,这部分的源代码如下所示:

public static ThreadPoolExecutor getDefaultExecutor(Configuration conf) {    int maxThreads = conf.getInt("hbase.htable.threads.max", Integer.MAX_VALUE);    if (maxThreads == 0) {      maxThreads = 1; // is there a better default?    }    long keepAliveTime = conf.getLong("hbase.htable.threads.keepalivetime", 60);    ThreadPoolExecutor pool = new ThreadPoolExecutor(1, maxThreads, keepAliveTime, TimeUnit.SECONDS,        new SynchronousQueue<Runnable>(), Threads.newDaemonThreadFactory("htable"));    pool.allowCoreThreadTimeOut(true);    return pool;}
ThreadPoolExecutor的构造函数和几个初始化参数的意义如下:

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

1、当线程池中的线程数小于corePoolSize时,无论是否有空闲线程,都会给新的任务分配新的线程;

2、当线程池中的线程数达到corePoolSize时,新提交的任务会被放入workQueue,等待线程池中的空闲线程调度处理;

4、如果workQueue已满,在池子中创建新的线程处理请求,直到线程池的大小到达maximumPoolSize,此时新提交任务由RejectedExecutionHandler来做拒绝处理;

5、当线程池的大小超过corePoolSize,空闲时间超过keepAliveTime的线程会被关闭。

也就是处理任务的优先级依次为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler进行拒绝处理,handler中可定义以下几种拒绝策略:

threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());        //直接丢弃threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());  //丢带队列头最老的任务ThreadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());     //重试ThreadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());          //抛出异常
BlockingQueue指定了workQueue为阻塞队列,依据消费数据的顺序可以将队列划分为先入先出和后入先出两种。队列的最常用应用场景是在消费者生产者模型中用于共享两者之间的数据。如果两者之间的数据处理速度发生了不匹配的情况,比如生产者的速度快于消费者,这样当生产者生产的数据累积到一定程度需要将生产者进程暂停一下(阻塞住)。阻塞队列就是用于处理上述情况的队列。

阻塞队列的所谓阻塞,是在某些情况下会挂起线程,一旦条件满足后,被挂起的线程又会自动被唤醒。下面介绍两种常见的阻塞场景:1、当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞,直到有数据放入队列。2、当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞,直到队列中有空的位置,线程被自动唤醒。

BlockingQueue的核心方法:

offer(Object):将一个对象添加到BlockingQueue里,如果执行成功返回true,否则直接返回false(失败时不阻塞当前执行offer方法的线程)。该函数还可以加超时参数,表示在超时时间范围内执行失败直接返回false。offer是贯彻了fail-fast设计原则的一个方法。引用bluedavy的话“所有的并发,外部访问阻塞的地方的一个真理就是一定要有超时机制”,“在线业务最强调的是快速处理掉一次请求”。

put(Object):把Object加到BlockingQueue里,如果队列没有空间,则阻塞调用此方法的线程直到队列BlockingQueue中有空间再继续。

poll(long timeout, TimeUnit unit):从BlockingQueue中取出队首对象,如果指定时间内有数据可取,则返回队列中的数据,否则返回失败。

take():取走BlockingQueue中排在首位的对象,若BlockingQueue为空,则阻塞消费者线程直到BlockingQueue中有新的数据加入。

drainTo():一次性从BlockingQueue中获取所有可用的对象。

下面介绍jdk中常用的几种阻塞队列:

ArrayBlockingQueue:一个基于数组实现的有界阻塞队列。ArrayBlockingQueue在生产者放入和消费者获取数据时共用一个锁对象,所以说对一个queue而言并没有做到真正的并行,该锁的实现默认采用非公平锁,这里所谓的公平是指当队列可用时,按阻塞的先后顺序访问队列,即先阻塞的生产者线程可以先往队列里插入数据,相同地先阻塞的消费者线程可以先从队列中获取数据。初始化ArrayBlockingQueue时可以改为公平锁,公平锁为保证公平性会降低吞吐量。

LinkedBlockingQueue:一个基于链表实现的有界阻塞队列。不同于ArrayBlockingQueue,其对生产者和消费者端分别采用了独立的锁来控制数据同步,因此提高了队列的并发性能。需要注意的是此队列的默认长度是Integer,MAX_VALUE,即无限大小,当生产者速度过快时,系统内存有被耗尽的风险。

DelayQueue:队列中的每个元素实现了Delayed接口,只有当指定的延迟时间到了,才能够从队列中获取该元素。DelayQueue可以用于保存将要执行的任务和执行时间,一旦从DelayQueue中获取到任务就表示任务可执行。或者用DelayQueue管理超时未响应的连接队列。

PriorityBlockingQueue:基于优先级的无界阻塞队列。

SynchronousQueue:不同于以上的阻塞队列,SynchronousQueue无数据缓冲区,相当于生产者和消费者直接交换数据而不通过任何中介。SynchronousQueue中每一个put操作必须等待一个take操作,否则不能继续添加元素。同理,offer的时候,如果没有另外一个线程正在take或者poll的话,那么offer就会失败;take的时候,如果没有另外的线程正好并发在offer,则take也会失败。SynchronousQueue默认采用非公平锁,同时使用一个LIFO队列来管理多余的生产者和消费者。SynchronousQueue的一个使用场景是在线程池中,Executors.newCachedThreadPool()就使用了SynchronousQueue,当新任务到来时线程池创建线程,如果有空闲线程会重复使用,超过60s的空闲线程会被回收。


0 0