3.2 线程复用:线程池

对应《实战java高并发程序设计》第三章3.2章节内容。

1.JDK线程池

1.1基本概念

避免系统频繁地创建和销毁线程,可以让创建的线程进行复用。当需要线程时,可以从池中拿一个空闲的线程,完成工作后,不着急关闭线程,而是还回池中。

1.2.jdk对线程池的支持

在java.util.concurrent包中,ThreadPoolExecutor表示一个线程池,Executors类则扮演着线程池工厂的角色,通过它可以取得一个特定功能的线程池。
在这里插入图片描述

在这里插入图片描述
可以看到有许多静态方法可以创建不同的线程池,下面介绍几种常见的:
注意:最后两个定时的返回的类型是ScheduledExecutorService ,它是线程池的子类,额外拥有定时等功能

		//返回一个固定线程数量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //返回只有一个线程的线程池,相当于上述方法参数为1的情况
        ExecutorService executorService2 = Executors.newSingleThreadExecutor();
        //数量不固定,有空闲,用空闲的,都没空闲,再创建。运行完成后,在返回
        ExecutorService executorService3 = Executors.newCachedThreadPool();
        //在给定时间执行某任务,线程池大小为1
        ScheduledExecutorService executorService4 = Executors.newSingleThreadScheduledExecutor();
        //在给定时间执行某任务,线程池大小为指定大小
        ScheduledExecutorService executorService5 = Executors.newScheduledThreadPool(5);
1.3线程池的使用
  • 通过submit来提交任务
  • 通过schedule来提交任务,后面跟上时间参数
	executorService.submit(new Runnable() {
            @Override
            public void run() {
                //....
            }
        });


        executorService4.schedule(new Runnable() {
            @Override
            public void run() {
               // ....
            }
        },1000,TimeUnit.SECONDS);

在这里插入图片描述
可根据需要选择需要的调度方式。

2.线程池的内部实现

2.1内部实现

通过看源码可以看到,上面创建普通线程池的方法,底层都是通过ThreadPoolExecutor来创建的。
在这里插入图片描述
我们来看看它的构造方法:

   /**
     * @param corePoolSize 
     * 指定数量,也就是说,即便是一个任务都没有,线程池也会至少有该数量个线程存活
     * @param maximumPoolSize 最大数量,当任务多
     * @param keepAliveTime 顾名思义,多余的空闲线程存活的时间
     * @param unit 时间单位
     * @param workQueue 任务队列,被提交,但还尚未被执行的任务
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
2.2workQueue详解

参数workQueue指被提交但未执行的任务队列,它是一个BlockingQueue接口的对象,用于存放Runnable对象。

  • 直接提交的队列:SynchronousQueue,没有容量,每一个插入都要等待一个删除,如果用这个队列,总是将新任务提交给线程去执行,线程数超过最大后,执行拒绝策略。
  • 有界的任务队列:ArrayBlockingQueue,有界队列仅在任务队列装满时,才会将线程提升到超过corePoolSize以上
  • 无界的任务队列:LinkedBlockingQueue,从上面分析一眼就能看出,它不可能等队列装满啦。当系统达到corePoolSize后,任务进队列,不会将线程继续提升了。
  • 优先任务队列:带有优先级的。
2.3线程池实现
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

3.拒绝策略

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

最后一个参数指定了拒绝策略,任务队列满了,线程也达到了最大值时,这时候拒绝策略就出场了。可以看到,这是个接口,有4中实现,也可以自己去实现。
在这里插入图片描述

  • 直接抛出异常
  • 在调用者线程中,运行当前被丢弃的任务
  • 丢弃最老的一个请求,然后再次提交当前任务
  • 默默抛弃,不做任务事情

4.自定义线程创建

我们通过ThreadPoolExecutor来创建线程池,在它的内部肯定有一个东西来根据参数生成线程,这是理所应当,从它的参数我们也能看到,有个TreadFactory参数。默认的都是用这个。
在这里插入图片描述

    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

这个类没多复杂,我们完全可以根据自己的需要重新实现一个。

5.扩展线程池

在ThreadPoolExecutor这个核心类上,我们能够看到三个空方法:都是protected,很明显,是让我们继承然后修改添加自己的逻辑的。

protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }

三个方法分别记录了一个任务的开始,结束和整个线程池的退出。

5.1 线程池的退出
/**
     * Initiates an orderly shutdown in which previously submitted
     * tasks are executed, but no new tasks will be accepted.
     * Invocation has no additional effect if already shut down.
     *
     * <p>This method does not wait for previously submitted tasks to
     * complete execution.  Use {@link #awaitTermination awaitTermination}
     * to do that.
     *
     * @throws SecurityException {@inheritDoc}
     */
    public void shutdown() {
    。。。。。。。。。。

6.优化线程池数量

一般考虑的是CPU数量和内存大小

7.在线程池中寻找堆栈

7.1 线程池中的一个坑

线程池有可能会吃掉程序抛出的异常,导致我们对程序的错误一无所知。异常堆栈对于程序员的重要性就像指南针对于大海的船只。

7.2 用submit()改用execute()?why

直接看源码就可以看出两者区别:
在这里插入图片描述
在这里插入图片描述

  • 最明显的一个区别是submit有返回值,我们可以通过这个Future来得到任务的一些信息。
7.3 扩展ThreadPoolExecutor线程池

让它在调度任务之前,先保存一下提交任务的堆栈信息。
任务的提交交到了我们自己手中,那还不是想怎么做就怎么做,下面就是在execute任务时,包装了一下,出错后打印信息。

public class MyThreadPoolExecutor extends ThreadPoolExecutor {

    @Override
    public void execute(Runnable command) {

        //在这里给任务线程包装下
        Runnable traceRunmable = new Runnable() {
            @Override
            public void run() {
                try{
                    command.run();
                }catch (Exception e){
                    System.out.println(Thread.currentThread().getName()+"出错了");
                    e.printStackTrace();
                    throw e;
                }
            }
        };

        super.execute(traceRunmable);
    }

8.Fork/Join框架

8.1介绍

可以向ForkJoinPool线程池提交一个FolkJoinTask任务,该任务即可以fork()分解,和join等待。
举个简单的例子,当我们需要计算1-10000的和的时候,我们可以把任务分解成1-5000和5001-10000两个子任务,当两个子任务完成后,自己才能将两个和再加起来。分解即fork,等待两个子任务完成即join

8.2可分解任务类 ForkJoinTask

它有两个重要的子类

  • RecursiveTask 有返回值
  • RecursiveAction 无返回值
    下面写一个计算1-10000的和:
/**
 * @author tanhao
 * @date 2020/10/16 14:35
 */
public class ForkJoinTaskStudy {


    public static void main(String[] args) throws Exception{
        ForkJoinPool pool = new ForkJoinPool();

        ForkJoinTask<Long> submit = pool.submit(new SumTask(1, 10000));

        Long aLong = submit.get();
        System.out.println(aLong);
    }
}

class SumTask extends RecursiveTask<Long>{

    private int start;
    private int end;

    public SumTask(int start,int end){
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {

        Long res = 0L;
        if (end - start > 1000){
            //分解成2个
            int count = (end - start)/2;
            SumTask first = new SumTask(start,start+count);
            SumTask last = new SumTask(start + count + 1, end);
            ForkJoinTask<Long> fork = first.fork();
            ForkJoinTask<Long> fork2 = last.fork();

            //等待子任务
            Long res1 = fork.join();
            Long res2 = fork2.join();

            res = res1+res2;

        }else{
            for (int i = start;i <= end;i++){
                res += i;
            }
        }

        return res;
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值