简介

Java通过Executors提供五种线程池,分别为:

  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • newWorkStealingPool:创建一个ForkJoin线程池,线程数是CPU核数,可以充分利用CPU资源

newCachedThreadPool

分析构造方法的参数

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
  • 初始线程数为:0
  • 最大线程数为:int的最大值
  • 超时时间为:1分钟
  • 阻塞队列采用的是:SynchronousQueue
  • 拒绝策略:默认策略(抛出异常)

根据参数可以得到以下结论

  • 这个线程池适用情况是短任务情况。
  • 采用SynchronousQueue,每当提交一个任务,都会超过阻塞队列的长度,导致创建线程,所以说:每当提交一个任务,都会创建一个线程,可能造成OOM。
  • 当线程空闲1分钟就会,销毁,所以该线程池会频繁的创建和销毁线程,最终会线程池会自己销毁
public class ExecutorsTest {

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newCachedThreadPool();

        System.out.println("当前的线程数为:"+executorService.getActiveCount());
        executorService.execute(()-> System.out.println("========="));

        IntStream.range(0,100).boxed().forEach(i->executorService.execute(()->{
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+ " [ "+  i +" ]");
        }));

        TimeUnit.SECONDS.sleep(2);
        System.out.println("当前的线程数为:"+executorService.getActiveCount());

    }
}

结果

当前的线程数为:0
=========
当前的线程数为:100
当前的线程数为:0

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
  • 初始线程数为:nThreads
  • 最大线程数为:nThreads
  • 超时时间为:0毫秒
  • 阻塞队列采用的是:LinkedBlockingQueue
  • 拒绝策略:默认策略(抛出异常)

根据参数可以得到以下结论

  • 该线程池的线程数是用户自定义的,不会增加,不会减少,线程池不会自己销毁
  • 阻塞队列是无限大的,不会执行拒绝策略。
  • 可能会堆集无限的请求,导致OOM

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  • 初始线程数为:1
  • 最大线程数为:1
  • 超时时间为:0毫秒
  • 阻塞队列采用的是:LinkedBlockingQueue
  • 拒绝策略:默认策略(抛出异常)

根据参数可以得到以下结论

  • 只有一个线程的固定线程池
  • 缺点和固定线程池一样
  • 只有ExecutorService方法

<mark>和一个线程的区别</mark>

newSingleThreadExecutor Thread
任务执行完成后,不会自动销毁,可以复用 任务执行完成后,会自动销毁
可以将任务存储在阻塞队列中,逐个执行 无法存储任务,只能执行一个任务

newWorkStealingPool


public static ExecutorService newWorkStealingPool(int parallelism) {
    return new ForkJoinPool
        (parallelism,
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}
public static ExecutorService newWorkStealingPool() {
    return new ForkJoinPool
        (Runtime.getRuntime().availableProcessors(),
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}


//Returns the number of processors available to the Java virtual machine.
Runtime.getRuntime().availableProcessors()


分析源码我们可以得知

  • 采用的ForkJoin框架,可以将任务进行分割,同时线程之间会互相帮助
  • 最大的线程数是CPU核数,充分利用CPU资源

newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
  • 创建的是一个定时的任务,每隔一段时间就会运行一次

首先可以对比的就是Timer这个类

public class ExecutorsTest {

    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();
        final TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("=====" + System.currentTimeMillis());
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        timer.schedule(task,0,1000);
    }
}

结果

=====1589162310908
=====1589162312910
=====1589162314911
=====1589162316911

可以发现:如果任务时间超过了定时时长,就无法按照预定的时间执行

其他工具的解决方式

  • crontab定时处理器为了确保时间的正确性,会重新启一个线程

有三个方法

  • schedule(commod,delay,unit) ,这个方法是说系统启动后,需要等待多久执行,delay是等待时间。只执行一次,没有周期性。

  • scheduleAtFixedRate(commod,initialDelay,period,unit),这个是以period为固定周期时间,按照一定频率来重复执行任务,initialDelay是说系统启动后,需要等待多久才开始执行。例如:如果设置了period为5秒,线程启动之后执行了大于5秒,线程结束之后,立即启动线程的下一次,如果线程启动之后只执行了3秒就结束了那执行下一次,需要等待2秒再执行。这个是优先保证任务执行的频率,

  • scheduleWithFixedDelay(commod,initialDelay,delay,unit),这个是以delay为固定延迟时间,按照一定的等待时间来执行任务,initialDelay意义与上面的相同。例如:设置了delay为5秒,线程启动之后不管执行了多久,结束之后都需要先生5秒,才能执行下一次。这个是优先保证任务执行的间隔。