JUC线程池:3大方、7大参数、4种拒绝策略、CPU密集型、IO密集型、混合型、线程池最大线程数确定

线程池

3大方法、7大参数、4种拒绝 自定义线程池

1、3大方法

不推荐使用该3大方法,请使用ThreadPoolExecutor创建线程池

  • Executors.newSingleThreadExecutor() 单个线程
  • Executors.newFixedThreadPool(int num) 固定数线程的线程池
  • Executors.newCacheThreadPool() 根据任务的情况,而规定线程池中的线程数
// newSingleThreadExecutor
/*
只允许一个线程执行,当核心线程数全部都在正在执行(第一个参数),进来的其他任务将会被加入到LinkedBlockingQueue队列中,由于这里的LinkedBlockingQueue的最大长度为Integer.MAX_VALUE,所以是无限大,由于会无限将任务存储到队列中,可能会造成OOM,最终会导致系统瘫痪,不安全。
由于LinkedBlockingQueue可以很大,所以等待时间参数以及最大线程数的设置是无效的。
*/
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

// newFixedThreadPool
/*
只允许nThreads线程执行,当核心线程数全部都在正在执行(第一个参数),进来的其他任务将会被加入到LinkedBlockingQueue队列中,由于这里的LinkedBlockingQueue的最大长度为Integer.MAX_VALUE,所以是无限大,由于会无限将任务存储到队列中,可能会造成OOM,最终会导致系统瘫痪,不安全。
由于LinkedBlockingQueue可以很大,所以等待时间参数以及最大线程数的设置是无效的。
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

// newCachedThreadPool
/*
该方法,核心线程数为0,而可创建的最大线程数却为Integer.MAX_VALUE,无限创建线程,也会造成OOM,一个新线程会创建一个栈空间,而栈空间就会占内存。SynchronousQueue队列是一个不存储数据的队列,即放入即取出。当没有任务可以使用时,闲着的线程会等待60L秒后,如果还是没有任务执行,这个线程就会被销毁关闭。
*/
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

通过3大方法,查看源码,发现这3大方法都new ThreadPoolExecutor该对象去创建线程池。阿里java开发手册强制规定,不可以使用Excutors工具类去创建线程池,因参数值为Integer.MAX_VALUE,最大线程支持21亿,这样高的并发量,会导致OOM (OutOfMemory内存溢出) 。阿里官方强烈推荐使用ThreadPoolExecutor来创建线程池

2、7大参数

ThreadPoolExecutor 类

public ThreadPoolExecutor(int corePoolSize, // 线程核心数(一直存在的线程数量)
                          int maximumPoolSize, // 能创建的最大线程数
                          long keepAliveTime, // 设定除核心线程以外的线程,超过该时间未使用就关闭
                          TimeUnit unit, // 设定时间的单位
                          BlockingQueue<Runnable> workQueue
                          // 阻塞队列,用于存储等待使用线程的任务
                          ThreadFactory threadFactory, // 固定
                          RejectedExecutionHandler handler
                          // 拒绝超额任务的方式(4种)
                         ) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

执行过程原理

任务一来,先检查核心线程是否已经被全部占用,如果没有全部占用,那么就使用核心线程数(核心线程数在初始化的时候就创建出来做准备的),如果已被全部占用,那么这个任务就会被加入到阻塞队列中。如果阻塞队列已满,那么就会创建新的线程,但是总线程数必须小于等于最大线程数。如果,最大线程数,每个线程都被占用,并且阻塞队列已满,那么就会根据拒绝方式返回不同的拒绝结果。

3、4种拒绝

// 最大限度任务:maximumPoolSize + workQueue
new ThreadPoolExecutor.AbortPolicy(); // 拒绝超过最大限度任务,并抛出异常
new ThreadPoolExecutor.CallerRunsPolicy(); // 哪儿来的任务回哪儿去,让你的原线程执行
new ThreadPoolExecutor.DiscardPolicy(); // 丢弃超过最大限度任务,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy(); // 进来的线程任务,尝试与最早的任务竞争,竞争不到,丢弃,不抛出异常

4、自定义线程池

public class PoolTest {
    public static void main(String[] args) {
        // 获取该运行时,CPU的核数,作为最大线程数
        int processors = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                        1,
                        processors,
                        3,
                        TimeUnit.SECONDS,
                        new ArrayBlockingQueue<>(3),
                        Executors.defaultThreadFactory(),
                        new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 1; i <= 8; i++) {
            poolExecutor.submit(()->{
                System.out.println(Thread.currentThread().getName() + " OK");
            });
        }

        poolExecutor.shutdown();
    }
}

输出:

pool-1-thread-2 OK
pool-1-thread-3 OK
pool-1-thread-1 OK
main OK
pool-1-thread-3 OK
pool-1-thread-4 OK
pool-1-thread-2 OK
main OK

CPU密集型 IO密集性 混合型

线程池最大线程数应该如何定义?

第一种:

CPU密集型:最大线程数应该等于CPU核数+1,这样最大限度提高效率。

// 通过该代码获取当前运行环境的cpu核数
Runtime.getRuntime().availableProcessors();

第二种:

IO密集型:主要是进行IO操作,执行IO操作的时间较长,这时cpu出于空闲状态,导致cpu的利用率不高。线程数为2倍CPU核数。当其中的线程在IO操作的时候,其他线程可以继续用cpu,提高了cpu的利用率。

第三种:

混合型:如果CPU密集型和IO密集型执行时间相差不大那么可以拆分;如果两种执行时间相差很大,就没必要拆分了。

第四种(了解):

在IO优化中,线程等待时间所占比越高,需要线程数越多;线程cpu时间占比越高,需要越少线程数。因此:

最佳线程数目=((线程等待时间+线程cpu时间)/ 线程cpu时间)* cpu数目

原文地址:https://www.cnblogs.com/turbo30/p/13688205.html