科技报道|一次性搞清楚,Java并发编程在各主流框架中的应用,保证看懂( 六 )

shutdownNow() {List tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(STOP);interruptWorkers();tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();return tasks;}public boolean isShutdown() {return ! isRunning(ctl.get());}}线程池执行流程 , 如下图所示 。
科技报道|一次性搞清楚,Java并发编程在各主流框架中的应用,保证看懂Executors 提供的 4 种线程池
Executors 类 通过 ThreadPoolExecutor 封装了 4 种常用的线程池:CachedThreadPool , FixedThreadPool , ScheduledThreadPool 和 SingleThreadExecutor 。 其功能如下 。

  1. CachedThreadPool:用来创建一个几乎可以无限扩大的线程池(最大线程数为 Integer.MAX_VALUE) , 适用于执行大量短生命周期的异步任务 。
  2. FixedThreadPool:创建一个固定大小的线程池 , 保证线程数可控 , 不会造成线程过多 , 导致系统负载更为严重 。
  3. SingleThreadExecutor:创建一个单线程的线程池 , 可以保证任务按调用顺序执行 。
  4. ScheduledThreadPool:适用于执行 延时 或者 周期性 任务 。
如何配置线程池
  • CPU 密集型任务尽量使用较小的线程池 , 一般为 CPU 核心数+1 。 因为 CPU 密集型任务 使得 CPU 使用率 很高 , 若开过多的线程数 , 会造成 CPU 过度切换 。
  • IO 密集型任务可以使用稍大的线程池 , 一般为 2*CPU 核心数 。 IO 密集型任务 CPU 使用率 并不高 , 因此可以让 CPU 在等待 IO 的时候有其他线程去处理别的任务 , 充分利用 CPU 时间 。
线程池的实际应用
Tomcat 在分发 web 请求时使用了线程池来处理 。
BlockingQueue核心方法
public interface BlockingQueue extends Queue {// 将给定元素设置到队列中 , 如果设置成功返回true, 否则返回false 。 如果是往限定了长度的队列中设置值 , 推荐使用offer()方法 。boolean add(E e);// 将给定的元素设置到队列中 , 如果设置成功返回true, 否则返回false. e的值不能为空 , 否则抛出空指针异常 。boolean offer(E e);// 将元素设置到队列中 , 如果队列中没有多余的空间 , 该方法会一直阻塞 , 直到队列中有多余的空间 。void put(E e) throws InterruptedException;// 将给定元素在给定的时间内设置到队列中 , 如果设置成功返回true, 否则返回false.boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException;// 从队列中获取值 , 如果队列中没有值 , 线程会一直阻塞 , 直到队列中有值 , 并且该方法取得了该值 。E take() throws InterruptedException;// 在给定的时间里 , 从队列中获取值 , 时间到了直接调用普通的 poll()方法 , 为null则直接返回null 。E poll(long timeout, TimeUnit unit)throws InterruptedException;// 获取队列中剩余的空间 。int remainingCapacity();// 从队列中移除指定的值 。boolean remove(Object o);// 判断队列中是否拥有该值 。public boolean contains(Object o);// 将队列中值 , 全部移除 , 并发设置到给定的集合中 。int drainTo(Collection c);// 指定最多数量限制将队列中值 , 全部移除 , 并发设置到给定的集合中 。int drainTo(Collection c, int maxElements);}主要实现类
  • ArrayBlockingQueue
基于数组的阻塞队列实现 , 在 ArrayBlockingQueue 内部 , 维护了一个定长数组 , 以便缓存队列中的数据对象 , 这是一个常用的阻塞队列 , 除了一个定长数组外 , ArrayBlockingQueue 内部还保存着两个整形变量 , 分别标识着队列的头部和尾部在数组中的位置 。 ArrayBlockingQueue 在生产者放入数据 和 消费者获取数据时 , 都是共用同一个锁对象 , 由此也意味着两者无法真正并行运行 , 这点尤其不同于 LinkedBlockingQueue 。 ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于 , 前者在插入或删除元素时不会产生或销毁任何额外的对象实例 , 而后者则会生成一个额外的 Node 对象 。 这在长时间内需要高效并发地处理大批量数据的系统中 , 其对于 GC 的影响还是存在一定的区别 。 而在创建 ArrayBlockingQueue 时 , 我们还可以控制对象的内部锁是否采用公平锁 , 默认采用非公平锁 。


推荐阅读