原因就是和synchronousQueue相关,上边已经简单介绍过该队列了,该队列的每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。如果mamximumPoolSize不设计得很大,那么就很容易抛出异常。
所以,使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。
3. newScheduledThreadPool
计划任务,和其他线程池不同,该线程池并不一定会立即安排任务,主要是起计划任务的作用。他会在指定时间、对任务进行调度。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue(), threadFactory);
}
scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。
schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间
4. newSingleThreadExecutor单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。
public static ExecutorService newSingleThreadExecutor() { return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory var0) { return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0)); }
5. newWorkStealingPool会根据所需的并行层次来动态创建一个拥有足够的线程数目的线程池。通过使用多个队列来降低竞争。并行的层次是和运行的最大线程数目相关。运行过程中实际的线程数目或许会动态地增长和收缩。
其本质是一个一个工作窃取的线程池,所以对于提交的任务不能保证是顺序执行的。底层用的ForkJoinPool来实现的。ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,
把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。分治的思想。下面是newWorkStealingPool的构造函数
public static ExecutorService newWorkStealingPool(int parallelism) { return new ForkJoinPool (parallelism,ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true); } //使用一个无限队列来保存需要执行的任务,可以传入线程的数量,不传入,则默认使用当前计算机中可用的cpu数量,使用分治法来解决问题,使用fork()和join()来进行调用 public ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, UncaughtExceptionHandler handler,boolean asyncMode) { this(checkParallelism(parallelism), checkFactory(factory), handler, asyncMode ? FIFO_QUEUE : LIFO_QUEUE,"ForkJoinPool-" + nextPoolId() + "-worker-"); checkPermission(); }
假设有3个线程A、B、C在运行,workStealing可以简单这么认为,每个线程都维护自己的一个队列,线程A的队列里头积累了3个任务,线程B的队列里2个任务,C的队列里1个任务;那么当线程C执行完任务之后,它会去别的线程池所维护的队列里面把任务偷过来继续执行,主动的找活干。
我们看一下newWorkStealingPool的使用案例
/** * WorkStealingPool(任务窃取,都是守护线程) * 每个线程都有要处理的队列中的任务,如果其中的线程完成自己队列中的任务, * 那么它可以去其他线程中获取其他线程的任务去执行 */ public class TestWorkStealingPool { public static void main(String[] args) throws IOException { // 根据cpu是几核来开启几个线程 ExecutorService service = Executors.newWorkStealingPool(); // 查看当前计算机是几核 System.out.println(Runtime.getRuntime().availableProcessors()); service.execute(new R(1000)); service.execute(new R(3000)); service.execute(new R(4000)); service.execute(new R(2000)); service.execute(new R(3000)); service.execute(new R(3000)); service.execute(new R(3000)); service.execute(new R(3000)); // WorkStealing是精灵线程(守护线程、后台线程),主线程不阻塞,看不到输出。 // 虚拟机不停止,守护线程不停止 System.in.read(); } static class R implements Runnable { int time; public R(int time) { this.time = time; } @Override public void run() { System.out.println(time + ":" + Thread.currentThread().getName() + "执行时间为:" + System.currentTimeMillis()); try { TimeUnit.MILLISECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } } }
结果输出