Executors专题

216 阅读3分钟

一、Executors创建线程

1、实例

使用线程池创建线程:

public class ExecutorTest {
    public static void main(String[] args) {
        ExecutorService thread = Executors.newSingleThreadExecutor();//创建单一线程
        //ExecutorService thread = Executors.newFixedThreadPool(15);//创建多个线程
        //ExecutorService thread = Executors.newCachedThreadPool();//根据情况创建线程
        try {
            for (int i = 0; i < 10; i++) {
                thread.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "执行");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            thread.shutdown();
        }
    }
}

2、ThreadPoolExecutor的七大参数

先了解一下ThreadPoolExecutor构造器:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    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;
}

这个构造方法里有7大参数:

• corePoolSize 线程池的核心线程数

• maximumPoolSize 能容纳的最大线程数

• keepAliveTime 空闲线程存活时间

• unit 存活的时间单位

• workQueue 存放提交但未执行任务的队列

• threadFactory 创建线程的工厂类

• handler 等待队列满后的拒绝策略

image-20211215130400441

四种拒绝策略:

image-20211215130432363

3、三种方式的源码

看看其中源码:

newSingleThreadExecutor()的:这里设置核心线程数和能容纳的最大线程数为1

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

newFixedThreadPool()的:这里设置核心线程数和能容纳的最大线程数为自己设置的值

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

newCachedThreadPool()的:这里没有核心线程数,而且能容纳的线程数最大约为21亿,太大了可能会报OOM

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

所以不太推荐直接使用Executors创建线程,最好还是自己使用ThreadPoolExecutor构造器创建。

image-20211215125116202

类比银行办理业务:corePoolSize就是正常工作的窗口,其他是忙的时候加的窗口,等待执行的任务队列类比一排排的等候区座椅。

4、创建线程和拒绝策略

public class ExecutorTest {
    public static void main(String[] args) {
        /**
         *  核心线程数2
         *  最大线程数5
         *  空闲线程等待多久关闭5s
         *  时间单位seconds
         *  队列
         *  默认创建线程,不用修改
         *  拒绝策略:
         *      AbortPolicy:如果队列满了会抛异常,一般超过maxPoolSize + queue就超出了
         *      CallerRunsPolicy:如果队列满了会让你从哪里来去哪里
         *      DiscardPolicy:队列满了不会抛出异常,直接丢掉任务
         *      DiscardOldestPolicy:尝试和最早的竞争,也不会抛出异常,没争过就丢掉
         */
        ThreadPoolExecutor thread = new ThreadPoolExecutor(
                2,
                5,
                5,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());
​
        try {
            for (int i = 0; i < 9; i++) {
                thread.execute(()->{
                    System.out.println( Thread.currentThread().getName() + "执行");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            thread.shutdown();
        }
    }
}

如何设置最大线程数:

  • cpu密集型:电脑有几核,就设置成多少
  • io密集型:判断有多少个比较耗费资源的io,比这个数稍微大一点即可

CPU密集型: 这种任务一般不占用大量IO,所以后台服务器可以快速处理,压力落在CPU上。

I/O密集型:常有大数据量的查询和批量插入操作,此时的压力主要在I/O上。

  • 与CPU密集型的关系:一般情况下,CPU核心数 == 最大同时执行线程数。在这种情况下(设CPU核心数为n),大量客户端会发送请求到服务器,但是服务器最多只能同时执行n个线程。所以这种情况下,无需设置过大的线程池工作队列,(工作队列长度 = CPU核心数 || CPU核心数+1)即可。
  • 与I/O密集型的关系:由于长时间的I/O操作,导致线程一直处于工作队列,但它又不占用CPU,则此时有1个CPU是处于空闲状态的。所以,这种情况下,应该加大线程池工作队列的长度,尽量不让CPU空闲下来,提高CPU利用率。

一般说来,线程池的大小应该怎么设置(线程池初始的默认核心线程数大小?)(其中 N为CPU的个数 )。

  • 如果是CPU密集型应用,则线程池大小设置为 N+1
  • 如果是IO密集型应用,则线程池大小设置为 2N+1
//查看电脑有多少核
System.out.println(Runtime.getRuntime().availableProcessors());