• 线程池复习笔记


    1. 线程池状态:

        runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;

        如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

        如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

        当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

    2. largestPoolSize只是一个用来起记录作用的变量,用来记录线程池中曾经有过的最大线程数目,跟线程池的容量没有任何关系。

    3. 比较重要成员变量:

        private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务

        private final ReentrantLock mainLock = new ReentrantLock(); //线程池的主要状态锁,对线程池状态(比如线程池大小runState等)的改变都要使用这个锁

        private final HashSet<Worker> workers = new HashSet<Worker>(); //用来存放工作集

        private volatile long keepAliveTime; //线程存活时间

        private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间

        private volatile int corePoolSize; //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)

        private volatile int maximumPoolSize; //线程池最大能容忍的线程数

        private volatile int poolSize; //线程池中当前的线程数

        private volatile RejectedExecutionHandler handler; //任务拒绝策略

        private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程

        private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数

        private long completedTaskCount; //用来记录已经执行完毕的任务个数

    1. 线程池状态:

        runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;

        状态值:RUNNING,SHUTDOWN,STOP,TERMINATED

    2. 任务的执行:

        1). 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;

        2). 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),

             则会尝试创建新的线程去执行这个任务;

        3). 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;

        4). 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,

             那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

    3. 线程池中的线程初始化

        默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

        在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

        prestartCoreThread():初始化一个核心线程;

        prestartAllCoreThreads():初始化所有核心线程

    4. 任务缓存队列及排队策略

        在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。

        workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

      1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

      2)LinkedBlockingQueue基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

             newFixedThreadPool,newSingleThreadExecutor线程池任务队列的实现。当线程池中没有可用线程时,新提交的任务将被加入到工作队列,等待有可用的空闲线程。

     3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

             newCachedThreadPool任务队列的实现,将任务直接提交而不保存,SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,因此线程池中如果不存在可用于直线任务的空闲线程,

             则新创建线程并运行。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。队列本身并不存储任何元素,非常适合于传递性场景,比如在一个线程中使用的数据,传递给另外一个

             线程使用,SynchronousQueue的吞吐量高于LinkedBlockingQueue 和 ArrayBlockingQueue[3]。

    5. 任务拒绝策略

        当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

         ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 默认处理策略。

         ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

         ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

         ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

    6. 线程池的关闭

         ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

         shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

         shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

    7. 线程池容量的动态调整

        ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

        setCorePoolSize:设置核心池大小

        setMaximumPoolSize:设置线程池最大能创建的线程数目大小

         当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

    8. 线程池生成方式:

        在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:

        1). Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE;newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,

             使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

             public static ExecutorService newCachedThreadPool() { 

                           return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue());

             }

            该线程池corePoolSize = 0, maximunPoolSize = Integer.MAX_VALUE,说明开始创建线程池后,并不会预创建线程,线程池最大线程数为Integer.MAX_VALUE,理论上可以拥有如此大的线程数。keepAliveTime = 60L,

            单位为Seconds,空闲线程会在等待60s之后被线程池回收掉。阻塞队列采用了SynchronousQueue,即有任务时会查询是否有空闲线程,如果有则自动执行任务,没有则创建新的线程,并执行任务。

            适用情形:适用于高并发,短任务的情形。线程使用完毕并不会立即回收线程,在长时间空闲时会自动回收线程。 

       2). Executors.newSingleThreadExecutor();  //创建容量为1的缓冲池,newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;

            public static ExecutorService newSingleThreadExecutor() {

                      return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()));

            }

           类似于newFixedThreadPool,nThreads = 1,线程池中只能运行一个线程,后续的任务将被加入到阻塞队列。

           适用情形:只能有一个运行的线程。

      3). Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池,newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

             public static ExecutorService newFixedThreadPool(int nThreads) {

                    return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

           }

           可以看出该方式中corePoolSize = maximumPoolSize = nThreads,即newFixedThreadPool一旦创建,则预先创建nThreads个线程并启动,如果后续任务加入进来时,有空闲线程,则使用,否则将任务加入到阻塞队列,

           等待可用线程。keepAliveTime = 0,线程池不会回收空闲线程。还有一个重载版本newFixedThreadPool(int nThreads, ThreadFactory threadFactory),可以自定义线程工厂。

           适用情形:固定的线程数量,预先创建线程,不会回收线程,抵消了创建/销毁线程所带来的额外开销,适用于稳定数量的并发线程。

       4). newWorkStealingPool:

             public static ExecutorService newWorkStealingPool(int parallelism) {

                   return new ForkJoinPool(parallelism,ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);

             }

            该线程池使用ForkJoinPool实现,参数parallelism为线程的并行数量其继承自AbstractExecutorService。ForkJoinPool实现了一个工作窃取算法,使得空闲线程能够窃取别的线程分解出来的子任务,

            从而让所有的线程都尽可能处于满负荷,提高执行效率。有一个无参数版本,其将parallelism设为Runtime.getRuntime().availableProcessors(),即处理器核数。

            ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。这种思想值得学习。

            ForkJoinTask是一个抽象类,它还有两个抽象子类:RecusiveAction和RecusiveTask。其中RecusiveTask代表有返回值的任务,而RecusiveAction代表没有返回值的任务。

            子类重写 compute 方法。 

            //并行执行两个 小任务

            left.fork();

            right.fork();

            //把两个小任务累加的结果合并起来

             left.join()+right.join();

             参见:https://www.cnblogs.com/lixuwu/p/7979480.html 

        5). newScheduledThreadPool

             public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {

                     return new ScheduledThreadPoolExecutor(corePoolSize);

             }

             通过ScheduledThreadPoolExecutor实现,通过corePoolSize指定返回一个ScheduledExecutorService,可以通过设置执行参数而指定任务延迟启动时间和执行周期。

             我们通过查看ScheduledThreadPoolExecutor的源代码,可以发现ScheduledThreadPoolExecutor的构造器都是调用父类的构造器,只是它使用的工作队列是java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue

             通过名字我们都可以猜到这个是一个延时工作队列.实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型

             和数量来进行配置。另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。

    9. 通过创建线程池返回的ExecutorService可以执行任务,执行任务主要有以下种方法:

        1). void execute(Runnable command) 执行一个实现了Runnable接口的任务,没有返回值。

        2). Future<?> submit(Runnable task) / Future submit(Runnable task, T result) / Future submit(Callable task):方法中的参数Callable类似于Runnable,只是在Callable接口可以返回任务的执行结果,而Runnable是没有返回值。

             这组任务执行提交方法提供了一个异步的Future返回值,可以通过Future去获得任务的返回值,并进行异步控制。通过Future中的isDown()可以判断任务是否结束,还可以通过cancel取消任务,通过isCancel判断任务是否取消,

             通过get方法将会使get所在的线程一直阻塞到任务结束获取结果。

            ExecutorService exec = Executors.newCachedThreadPool();

            exec.execute(() -> {

                   System.out.println("Runnable task");

             }) 

            Future result1 = exec.submit(() -> {

                         System.out.println("Callable task");

                         return 2 * 3;

              });

             result1.get();

        3). T invokeAny(Collection<? extends Callable> tasks) / T invokeAny(Collection<? extends Callable> tasks,long timeout, TimeUnit unit)

              这一组方法是批量提交任务的方法,其中invokeAny方法:阻塞主线程直到有一个任务执行完毕并自动取消剩余正在执行的任务,返回执行完毕任务的结果。long time提供了一个等待超时参数。

        4). invokeAll(Collection<? extends Callable> tasks) / List<Future> invokeAll(Collection<? extends Callable> tasks,long timeout, TimeUnit unit)

             invokeAll方法类似于invookeAny方法,区别在于:invokeAll阻塞主线程直到所有任务执行完毕,并返回任务执行Future集合。同样long time提供了一个超时参数。

        5). schedule / scheduleAtFixedRate / scheduleWithFixedRate

             a).schedule(Callable callable, long delay, TimeUnit unit):将任务延迟delay时间执行,使用Callable接口。

             b).scheduleAtFixedRate(Runnable command, long initalDelay, long period, TimeUnit unit):该方法会将任务延迟initalDelay时间并循环执行,周期为period,下个执行时间点为从任务启动执行后period时间。同样TimeUnit为时间单位。

             c).scheduleWithFixedRate(Runnable command, long initalDelay, long period, TimeUnit unit):类似于上一个方法,但是这里任务延迟开始执行后,下个任务周期执行时间是从上个任务结束时算起。

     

    public class Test {
         public static void main(String[] args) {   
             ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                     new ArrayBlockingQueue<Runnable>(5));
              
             for(int i=0;i<15;i++){
                 MyTask myTask = new MyTask(i);
                 executor.execute(myTask);
                 System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
                 executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
             }
             executor.shutdown();
         }
    }
     
     
    class MyTask implements Runnable {
        private int taskNum;
         
        public MyTask(int num) {
            this.taskNum = num;
        }
         
        @Override
        public void run() {
            System.out.println("正在执行task "+taskNum);
            try {
                Thread.currentThread().sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task "+taskNum+"执行完毕");
        }
    }

    思想:

    1. shutdown() 和 shutdownNow(),通过一个状态值来标记状态。

    2. corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。

    3. 这里有一个非常巧妙的设计方式,假如我们来设计线程池,可能会有一个任务分派线程,当发现有线程空闲时,就从任务缓存队列中取一个任务交给空闲线程执行。但是在这里,并没有采用这样的方式,

        因为这样会要额外地对任务分派线程进行管理,无形地会增加难度和复杂度,这里直接让执行完任务的线程去任务缓存队列里面取任务来执行。

    4. 先不加锁判断,如果真的需要改变的时候,再加锁,再判断一次,再改变

    try {} 和 finally {}一起使用,可以不需要 catch(){}

  • 相关阅读:
    vim cheat
    latex base
    latex font
    lstings
    使用React 如何设计 模板自定义的框架
    react hooks 的更进一步适应性使用
    IDEA反编译jar包源码
    Redis Lua实战
    Spring AOP拦截并打印controller层请求日志
    漏桶算法和令牌桶算法的区别
  • 原文地址:https://www.cnblogs.com/Jtianlin/p/8641283.html
Copyright © 2020-2023  润新知