1、线城池介绍

(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;

(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;

(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场)

(4)提供更强大的功能,延时定时线程池。

2、线城池创建方法

Executors是concurrent包下的一个类,为我们提供了创建线程池的简便方法。

Executors可以创建我们常用的四种线程池:

(1)newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。不设上限,提交的任务将立即执行。

(2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

(3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

(4)newSingleThreadExecutor 创建一个单线程化的线程池执行任务。

但是以上四种创建方式有很弊端:

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

根据阿里规范不允许

Java线城池详解_执行时间

再者LinkedBlockingQueue并没有指定队列的大小是一个无界队列,这样可能会造成oom。

3、自定义线城池

ThreadPoolExecutor 类中参数最多的构造方法:

orePoolSize: 线程池核心线程数,当初始化线程池时,会创建核心线程进入等待状态,即使它是空闲的,核心线程也不会被摧毁,从而降低了任务一来时要创建新线程的时间和性能开销。当allowCoreThreadTimeOut手动设置为true,才会销毁

maximumPoolSize: 最大线程数,意味着核心线程数都被用完了,那只能重新创建新的线程来执行任务,但是前提是不能超过最大线程数量,否则该任务只能进入阻塞队列进行排队等候,直到有线程空闲了,才能继续执行任务

keepAliveTime: 线程存活时间,除了核心线程外,那些被新创建出来的线程可以存活多久。意味着,这些新的线程一但完成任务,而后面都是空闲状态时,就会在一定时间后被摧毁

**threadFactory:**就是创建线程的线程工厂

unit: 线程存活时间单位

workQueue: 表示任务的阻塞队列,由于任务可能会有很多,而线程就那么几个,所以那么还未被执行的任务就进入队列中排队,队列我们知道是 FIFO 的,等到线程空闲了,就以这种方式取出任务。这个一般不需要我们去实现。handler的拒绝策略有四种:

(1)AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
  (2)DisCardPolicy:不执行新任务,也不抛出异常
  (3)DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
  (4)CallerRunsPolicy:直接调用execute来执行当前任务

经常用得队列

\1. LinkedBlockingQueue

  对于 FixedThreadPool 和 SingleThreadExector 而言,它们使用的阻塞队列是容量为 Integer.MAX_VALUE 的 LinkedBlockingQueue,可以认为是无界队列。由于 FixedThreadPool 线程池的线程数是固定的,所以没有办法增加特别多的线程来处理任务,这时就需要 LinkedBlockingQueue 这样一个没有容量限制的阻塞队列来存放任务。这里需要注意,由于线程池的任务队列永远不会放满,所以线程池只会创建核心线程数量的线程,所以此时的最大线程数对线程池来说没有意义,因为并不会触发生成多于核心线程数的线程。

\2. SynchronousQueue

  第二种阻塞队列是 SynchronousQueue,对应的线程池是 CachedThreadPool。线程池 CachedThreadPool 的最大线程数是 Integer 的最大值,可以理解为线程数是可以无限扩展的。CachedThreadPool 和上一种线程池 FixedThreadPool 的情况恰恰相反,FixedThreadPool 的情况是阻塞队列的容量是无限的,而这里 CachedThreadPool 是线程数可以无限扩展,所以 CachedThreadPool 线程池并不需要一个任务队列来存储任务,因为一旦有任务被提交就直接发给线程或者创建新线程来执行,而不需要另外保存它们。

我们自己创建使用 SynchronousQueue 的线程池时,如果不希望任务被拒绝,那么就需要注意设置最大线程数要尽可能大一些,以免发生任务数大于最大线程数时,没办法把任务放到队列中也没有足够线程来执行任务的情况。

\3. DelayedWorkQueue

  第三种阻塞队列是DelayedWorkQueue,它对应的线程池分别是 ScheduledThreadPool 和 SingleThreadScheduledExecutor,这两种线程池的最大特点就是可以延迟执行任务,比如说一定时间后执行任务或是每隔一定的时间执行一次任务。DelayedWorkQueue 的特点是内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构。之所以线程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 选择 DelayedWorkQueue,是因为它们本身正是基于时间执行任务的,而延迟队列正好可以把任务按时间进行排序,方便任务的执行。

Java线城池详解_执行时间_02

4、如何配置线程池

CPU密集型任务

尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

IO密集型任务

可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

混合型任务

可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。

因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。

因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

5、线城池工具类

public final class ThreadPoolProvider {
private ThreadPoolProvider(){}

/**
* 线程池<br/>
* 备注:不建议随处创建,可统一一处调用
*/
private static final ExecutorService FIXED_THREAD_POOL = new ThreadPoolExecutor(10,500,5,TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable> (1000),
new DefaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

/**
* @Title newFixedThreadPool
* @Description: 获取定长的线程池
* @return ExecutorService
*/
public static ExecutorService newFixedThreadPool(){
return FIXED_THREAD_POOL;
}


/**
* @ClassName: DefaultThreadFactory
* @Description: 线程命名工厂
*/
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
this("thread-pool-t-");
}
DefaultThreadFactory(String prefix) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = prefix+poolNumber.getAndIncrement();
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon()) t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
}

关注我的微信公众号

​​​Java线城池详解_线程池_03