• 线程池基本使用和ThreadPoolExecutor核心原理讲解


    原文地址:https://www.jianshu.com/p/ec5b8cccd87d

    java和spring都提供了线程池的框架

    java提供的是Executors;
    spring提供的是ThreadPoolTaskExecutor;

    一、基本使用

    Executors提供了4个线程池,

    1. FixedThreadPool
    2. SingleThreadExecutor
    3. CachedThreadPool
    4. ScheduledThreadPool

    FixedThreadPool-固定线程池

    public class FixPool {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newFixedThreadPool(5);
            for (int i =0;i<10;i++){
                executorService.execute(()->{
                    Thread wt = Thread.currentThread();
    
                    String format = String.format("当前线程名称:%s,当前时间:%s", wt.getName(), LocalDateTime.now());
                    System.out.println(format);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
            executorService.shutdown();
        }
    }
    

    效果如下:

    可以看到5个线程在接任务,接完5个任务之后就停止了1秒,完成之后继续接任务;

    SingleThreadExecutor-单一线程池,线程数为1的固定线程池

    为什么要创造这么一个线程池出来呢?
    因为有些时候需要用到线程池的队列任务机制,又不想多线程并发。此时就需要用单一线程池了。
    以下两种写法完全一样的效果

    ExecutorService executorService = Executors.newSingleThreadExecutor();
    
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    

    CachedThreadPool-缓存线程池,线程数为无穷大的一个线程池

    当有空闲线程的时候就让空闲线程去做任务;
    当没空闲线程的时候就新建一个线程去任务;

    public class CashPool {
        public static void main(String[] args) throws InterruptedException {
            ExecutorService executorService = Executors.newCachedThreadPool();
    
            for (int i =0;;i++){
                executorService.execute(()->{
                    Thread wt = Thread.currentThread();
    
                    String format = String.format("缓存线程池,当前线程名称:%s,当前时间:%s", wt.getName(), LocalDateTime.now());
                    System.out.println(format);
                    try {
                        double d = Math.random();
                        int sleep = (int)(d*5000);
                        Thread.sleep(sleep);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
                Thread.sleep(100);
            }
        }
    }
    

    效果如下:

    由于任务耗时不确定,所以线程池会动态根据情况去判断是否创建新的线程;

    ScheduledThreadPool-调度线程池,线程会根据一定的时间规律去消化任务

    分别有3个

    1. schedule(固定延时才执行任务)
    2. scheduleAtFixedRate(一定的间隔执行一次任务,执行时长不影响间隔时间)
    3. scheduleWithFixedDelay(一定的间隔执行一次任务,从任务执行接触才开始计算,执行时长影响间隔时间)
    public class ScheduledPool {
        public static void main(String[] args) {
            ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
            String begin = String.format("调度线程池-begin,当前时间:%s",  LocalDateTime.now());
            System.out.println(begin);
    
    //        schedule(pool);
            scheduleAtFixedRate(pool);
    //        scheduleWithFixedDelay(pool);
        }
    
        private static void scheduleAtFixedRate(ScheduledExecutorService pool){
            pool.scheduleAtFixedRate(()->{
                Thread wt = Thread.currentThread();
                String format = String.format("scheduleAtFixedRate-调度线程池,当前线程名称:%s,当前时间:%s", wt.getName(), LocalDateTime.now());
                System.out.println(format);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },1,1, TimeUnit.SECONDS);
        }
    
    
        private static void scheduleWithFixedDelay(ScheduledExecutorService pool){
            pool.scheduleWithFixedDelay(()->{
                Thread wt = Thread.currentThread();
                String format = String.format("scheduleWithFixedDelay-调度线程池,当前线程名称:%s,当前时间:%s", wt.getName(), LocalDateTime.now());
                System.out.println(format);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },1,1, TimeUnit.SECONDS);
        }
        private static void schedule(ScheduledExecutorService pool){
            for (int i =0;i<10;i++){
                pool.schedule(()->{
                    Thread wt = Thread.currentThread();
                    String format = String.format("调度线程池,当前线程名称:%s,当前时间:%s", wt.getName(), LocalDateTime.now());
                    System.out.println(format);
                },5, TimeUnit.SECONDS);
            }
            pool.shutdown();
        }
    }
    

    二、原理讲解

    上面介绍的4个线程池工具,都是基于一个类ThreadPoolExecutor
    ThreadPoolExecutor有几个重要的参数

    1. corePoolSize,核心线程数
    2. maximumPoolSize,最大线程数
    3. keepAliveTime,线程空闲时间,和4组合使用
    4. unit
    5. workQueue,工作队列,是各个工具的机制策略的核心
    6. threadFactory ,生成线程的工厂,可以代理run方法,还有给thread命名
    7. handler,拒绝策略,当任务太多的时候会执行的方法,框架有4个实现好的策略,可以自己写自己的策略。

    对于fixPool线程池,corePoolSize=maximumPoolSize=n,keepAliveTime=0,workQueue=LinkedBlockingQueue,threadFactory和handler都是默认的。

    对于cashPool线程池,corePoolSize=0,maximumPoolSize=2^32,keepAliveTime=60s,workQueue=SynchronousQueue,threadFactory和handler都是默认的。

    我们先看一看execute方法

    其中ctl参数是一个核心参数,保存着线程池的运行状态和线程数量,通过workerCountOf()获取当前的工作线程数量。
    execute整个过程分成3个部分。

    1. 当工作线程数少于核心线程数,创建一个工作线程去消化任务。
    2. 当线程池在运行状态而且能把任务放到队列,则接受任务,调用addWorker让线程去消化队列的任务。
    3. 让线程获取消化任务失败,拒绝任务。

    核心一 workQueue.offer

    对于fixPool,由于workQueue是LinkedBlockingQueue,所以offer方法基本会返回true。
    对于cashpool,workQueue是SynchronousQueue,如果没有消费者在take,则会立马返回false,然后立马新建一个线程。

    核心二 getTask

    每个线程都被包装成worker对象,worker对象会执行自己的runWorker方法,方法在死循环不停得调用getTask方法去消化任务。

    getTask里面最核心的是take和poll方法,这个是跟你传入的队列特性有关。

    对于spring提供的ThreadPoolTaskExecutor,其实也是对ThreadPoolExecutor的一个封装。
    具体看initializeExecutor方法

    在执行execute方法的时候,也是执行ThreadPoolExecutor的execute方法。

    结论,线程池的核心是ThreadPoolExecutor类,根据传入的workQueue的offer、poll、take方法特性的不同而诞生缓存线程池,固定线程池,调度线程等各种线程策略。

    github地址:https://github.com/hd-eujian/threadpool.git
    码云地址: https://gitee.com/guoeryyj/threadpool.git

  • 相关阅读:
    python bilibili(一)获取直播间标题
    反射机制
    如何修改excel转换csv的分隔符
    Timer和TimerTask详解——(转)
    Android完全退出应用程序(一)
    动态设置ImageView中的图片
    excel导入sqlite (命令方式)
    Android开发中完全退出程序的三种方法
    Android实现回退
    如何把批量数据导入到android 的 sqlite 数据库(转)
  • 原文地址:https://www.cnblogs.com/yeyongjian/p/13433973.html
Copyright © 2020-2023  润新知