关于线程池的部分内容

40 阅读4分钟

----------------关于线程池的部分内容------------------
ThreadPoolExecutor中execute和submit的区别

4种模式的线程池创建
手动创建(阿里推荐,可以更透明,更灵活,更容易理解)

1:入参不同
  excute() 传入的是 Runable,
  submit 传入的是 Callable 或 Runable

  1):execute 方法源码

public void execute(Runnable command) {
         ...
  }

  2):submit方法源码

  public Future<?> submit(Runnable task) {
      ...
   }
    public <T> Future<T> submit(Runnable task, T result) {
        ...
  }
    public <T> Future<T> submit(Callable<T> task) {
       ...
  }

2:回参不同
  execute 无返回值,submit 有返回值
  1):submit 底层调用的还是 execute 方法,传入Callable 也会转化为 Runable 并交给 execute方法处理。
  2):对submit 方法返回的结果(Future)进行 get() 的时候会导致调用方阻塞,直至 submit 内部异常或成功返回结果。
  3):submit 的异常封装在结果(Future)中,要调用 get() 方法才会抛出异常,否则无法抛出异常。
package study4;

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4,
                60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        return new Thread(r, "线程");
                    }
                }, new RejectedExecutionHandler() {
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            System.out.println("执行拒绝策略");
                            try {
                                throw new RejectedExecutionException();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                }
        );

        Future<?> submit1 = threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("submit1执行任务");
            }
        });

        Future<String> submit2 = threadPoolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "submit2执行了任务";
            }
        });

        final String[] res = {new String("有什么用?")};
        Future<String> submit3 = threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("submit3执行了任务");
                res[0] = new String("123");
                return;
            }
        }, res[0]);

        try {
            System.out.println("submit2 get "+submit2.get());
            System.out.println("submit3 get "+ res[0]);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        threadPoolExecutor.shutdown();
    }
}

一、线程池的拒绝策略
线程池中,有三个重要的参数,决定影响了拒绝策略:
corePoolSize - 核心线程数,也即最小的线程数。
workQueue - 阻塞队列 。
maximumPoolSize - 最大线程数
当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。总结起来,也就是一句话,当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略。


二、拒绝策略定义
拒绝策略提供顶级接口 RejectedExecutionHandler ,其中方法 rejectedExecution 即定制具体的拒绝策略的执行逻辑。
jdk默认提供了四种拒绝策略:
CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
DiscardPolicy - 直接丢弃,其他啥都没有
DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
三、测试代码
1、AbortPolicy
public static void main(String[] args) throws Exception{
        int corePoolSize = 5;
        int maximumPoolSize = 10;
        long keepAliveTime = 5;
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(10);
        RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, handler);
        for(int i=0; i<100; i++) {
            try {
                executor.execute(new Thread(() -> log.info(Thread.currentThread().getName() + " is running")));
            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }
        }
        executor.shutdown();
    }

executor.execute()提交任务,由于会抛出 RuntimeException,如果没有try.catch处理异常信息的话,会中断调用者的处理流程,后续任务得不到执行(跑不完100个)。可自行测试下,很容易在控制台console中能查看到。

2、CallerRunsPolicy
主体代码同上,更换拒绝策略:
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
运行后,在控制台console中能够看到的是,会有一部分的数据打印,显示的是 “main is running”,也即体现调用线程处理。
3、DiscardPolicy
更换拒绝策略
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
直接丢弃任务,实际运行中,打印出的信息不会有100条。
4、DiscardOldestPolicy
同样的,更换拒绝策略:
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
实际运行,打印出的信息也会少于100条。

四、总结
四种拒绝策略是相互独立无关的,选择何种策略去执行,还得结合具体的业务场景。实际工作中,一般直接使用 ExecutorService 的时候,都是使用的默认的 defaultHandler ,也即 AbortPolicy 策略。