• 22、线程池


     

    一、为什么需要线程池

    ▪ 在实际使用中,线程是很占用系统资源的,如果对线程管理不善
      很容易导致系统问题。因此,在大多数并发框架中都会使用线程
      池来管理线程,使用线程池管理线程主要有如下好处:
      – 1、使用线程池可以重复利用已有的线程继续执行任务,避免线程在创建和
        销毁时造成的消耗
      – 2、由于没有线程创建和销毁时的消耗,可以提高系统响应速度
      – 3、通过线程可以对线程进行合理的管理,根据系统的承受能力调整可运行
        线程数量的大小等

     任务其实就是写的代码,提交任务就是代码逻辑,是需要线程来运行的

    从线程池里面拿取数据,里面有具体的执行过程

    核心线程池:开始无法预估系统, 假如预设值30个,不管有没有用户的连接,就保持30个,这就是核心线程池的数量

      根据业务需求,并发访问量决定就好

    阻塞队列:只有30个线程,但是同一时刻提交了300个任务,不可能同时执行300个,先执行30个,剩下的270个放在任务队列中

    线程池:比如有个水缸,平常只放半缸水就好,它里面有一个max最大容量,最大容量如果没满就创建线程执行任务

    饱和策略又叫拒绝策略
     
    ▪ 线程池执行所提交的任务过程:
    ▪ 1、先判断线程池中核心线程池所有的线程是否都在执行任务。
      如果不是,则新创建一个线程执行刚提交的任务,否则,核心线
      程池中所有的线程都在执行任务,则进入第2步;
    ▪ 2、判断当前阻塞队列是否已满,如果未满,则将提交的任务放
      置在阻塞队列中;否则,则进入第3步;
    ▪ 3、判断线程池中所有的线程是否都在执行任务,如果没有,则
      创建一个新的线程来执行任务,否则,则交给饱和策略进行处理

    二、线程池的分类

    ThreadPoolExecutor:线程池执行器

    ScheduledThreadPoolExecutor:可调度的线程池执行器

    ForkJoinPool:JDK1.7后新出现的,分组组合, 分而治之

      1-100的和 50*101=5050

    1、ThreadPoolExecutor newCachedThreadPool(无界)

     

    public class CacheThreadPoolDemo {
    
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            for(int i = 0;i<20;i++){
                executorService.execute(new Task());
            }
            executorService.shutdown();
        }
    
    }
    public class Task implements Runnable {
        @Override
        public void run() {
    //        try {
    //            Thread.sleep(1000);
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }
            System.out.println(Thread.currentThread().getName()+" running");
        }
    }

    打印结果为:

     会出现线程的重用, 加个睡眠会发现创建了20个线程

     这里面没见到start,是因为是线程池触发的

    /*
    创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用他们, 并在需要时提供的ThreadFactory创建新线程
    
    特征:
    (1) 线程池中数量没有固定, 可达到最大值(Interger, MAX_VALUE) 21亿
    (2) 线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
    (3) 当线程池中, 没有可用线程, 会重新创建一个线程
    */

    2、ThreadPoolExecutor newFixedThreadPool(指定大小)

    public class FixedThreadPoolDemo {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newFixedThreadPool(5); // 最多容纳多少个线程
            for (int i = 0 ;i<20;i++){
                executorService.execute(new Task());
            }
            executorService.shutdown();
        }
    }

    打印结果为:

    睡眠打开后, 5个一执行

    重用指定好的线程池

    3、ThreadPoolExecutor newSingleThreadExecutor(单一)

    public class SingleThreadPoolDemo {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            for(int i = 0;i<20;i++){
                executorService.execute(new Task());
            }
            executorService.shutdown();
        }
    }

    打印结果为:

     为什么有这么多分类???

     这些最终都是执行的ThreadPoolExecutor(), 只是参数不同

    最终要学习的就是ThreadPoolExecutor

    有定时任务, java里有Timer类 quartz 但是多线程里想操作线程的延时执行必须用调度框架

    4、ScheduledThreadPoolExecutor newScheduledThreadPool

    3s后执行

    public class ScheduledThreadPoolDemo {
        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
            System.out.println(System.currentTimeMillis());
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("延迟三秒执行");
                    System.out.println(System.currentTimeMillis());
                }
            },3, TimeUnit.SECONDS);
            scheduledExecutorService.shutdown();
        }
    }

     每3秒执行一次

    public class ScheduledThreadPoolDemo2 {
        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
            System.out.println(System.currentTimeMillis());
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("1------延迟一秒执行,每三秒执行一次");
                    System.out.println(System.currentTimeMillis());
                }
            },1,3, TimeUnit.SECONDS);
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("2------延迟一秒执行,每三秒执行一次");
                    System.out.println(System.currentTimeMillis());
                }
            },1,3, TimeUnit.SECONDS);
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("3-------延迟一秒执行,每三秒执行一次");
                    System.out.println(System.currentTimeMillis());
                }
            },1,3, TimeUnit.SECONDS);
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("4--------延迟一秒执行,每三秒执行一次");
                    System.out.println(System.currentTimeMillis());
                }
            },1,3, TimeUnit.SECONDS);
    //        scheduledExecutorService.shutdown();
        }
    }

    什么样的场景会用到分而治之

    1、实例1

    import java.util.concurrent.RecursiveAction;
    
    class PrintTask extends RecursiveAction {
        private static final int THRESHOLD = 50; //最多只能打印50个数
        private int start;
        private int end; 
      
        public PrintTask(int start, int end) {
            super();
            this.start = start;
            this.end = end;
        }
    
        @Override
        protected void compute() {
            
            if(end - start < THRESHOLD){
                for(int i=start;i<end;i++){
                    System.out.println(Thread.currentThread().getName()+"的i值:"+i);
                }
            }else {
                int middle =(start+end)/2;
                PrintTask left = new PrintTask(start, middle);
                PrintTask right = new PrintTask(middle, end);
                //并行执行两个“小任务”
                left.fork();
                right.fork();
            }
            
        }
        
    }
    /**
     *
     * 简单的打印0-300的数值。用多线程实现并行执行
     *
     */
    public class ForkJoinPoolAction {
        
        public static void main(String[] args) throws Exception{
            PrintTask task = new PrintTask(0, 300);
            //创建实例,并执行分割任务
            ForkJoinPool pool = new ForkJoinPool();
            pool.submit(task);
             //线程阻塞,等待所有任务完成
            pool.awaitTermination(2, TimeUnit.SECONDS);
            pool.shutdown();
        }
    }

     不同的worker来执行的, 300个数

    300 等分 150下

    150 等分 75 下

    75 等分 37 38 下

    300 - 0 < 50 ? 进入 else

    middle 150

    left 0 150

    right 150 300

    就这样一直进行切分

    2、实例2

    import java.util.concurrent.RecursiveTask;
    
    class SumTask extends RecursiveTask<Integer> {
        private static final int THRESHOLD = 20; //每个小任务 最多只累加20个数
        private int arry[];
        private int start;
        private int end;
        
        
    
        /**
         * Creates a new instance of SumTask.
         * 累加从start到end的arry数组
         * @param arry
         * @param start
         * @param end
         */
        public SumTask(int[] arry, int start, int end) {
            super();
            this.arry = arry;
            this.start = start;
            this.end = end;
        }
    
    
    
        @Override
        protected Integer compute() {
            int sum =0;
            //当end与start之间的差小于threshold时,开始进行实际的累加
            if(end - start <THRESHOLD){
                for(int i= start;i<end;i++){
                    System.out.println(Thread.currentThread().getName()+"的i值:"+arry[i]);
                    sum += arry[i];
                }
                return sum;
            }else {//当end与start之间的差大于threshold,即要累加的数超过20个时候,将大任务分解成小任务
                int middle = (start+ end)/2;
                SumTask left = new SumTask(arry, start, middle);
                SumTask right = new SumTask(arry, middle, end);
                //并行执行两个 小任务
                left.fork();
                right.fork();
                //把两个小任务累加的结果合并起来
                return left.join()+right.join();
            }
            
        }
        
    }
    import java.util.Random;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.Future;
    
    public class ForJoinPollTask {
    
        public static void main(String[] args) throws Exception {
            int[] arr = new int[100];
            Random random = new Random();
            int total =0;
            //初始化100个数组元素
            for(int i=0,len = arr.length;i<len;i++){
                int temp = random.nextInt(20);
                //对数组元素赋值,并将数组元素的值添加到sum总和中
                total += (arr[i]=temp);
            }
            System.out.println("初始化数组总和:"+total);
            SumTask task = new SumTask(arr, 0, arr.length);
    //        创建一个通用池,这个是jdk1.8提供的功能
            ForkJoinPool pool = ForkJoinPool.commonPool();
            Future<Integer> future = pool.submit(task); //提交分解的SumTask 任务
            System.out.println("多线程执行结果:"+future.get());
            pool.shutdown(); //关闭线程池
            
            
    
        }
    
    }

    3、实例3

    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
     
    public class newWorkStealingPoolTest {
     
        public static void main(String[] args) throws Exception {
     
            // 设置并行级别为2,即默认每时每刻只有2个线程同时执行
            ExecutorService m = Executors.newWorkStealingPool();
     
            for (int i = 1; i <= 10; i++) {
                final int count=i;
                m.submit(new Runnable() {
                    @Override
                    public void run() {
                        Date now=new Date();
                        System.out.println("线程" + Thread.currentThread() + "完成任务:"
                                + count+"   时间为:"+    now.getSeconds());
                        try {
                            Thread.sleep(1000);//此任务耗时1s
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
     
                });
               
            }
            while(true){
                //主线程陷入死循环,来观察结果,否则是看不到结果的
            }
        }
    }

    三、线程池的生命周期

    ▪ RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
    ▪ SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻
      塞队列中已保存的任务。
    ▪ STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。
    ▪ TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,
      线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
    ▪ TERMINATED:在terminated() 方法执行完后进入该状态,默认
      terminated()方法中什么也没有做。

    面试:

    线程生命周期只有两种状态,RUNNING和TERMINATED,但是在从RUNNING到TERMINATED这个状态过度的时候

    会有中间3种状态,第一种调用shutdown()时会切换到SHUTDOWN,第二种调用shutdown Now() 时切换到STOP,

    最终会进行一个回收的工作,SHUTDOWN回收时当前正在执行的任务会执行完(阻塞队列为空,线程池中的工作线程数量为0)

    进入到TIDYING

    STOP队列里有任务也会直接干掉,不是等待执行完再干掉

    之前我们说不管哪个分类都调用ThreadPoolExecutor

    四、ThreadPoolExecutor

    构造方法

     

    ▪ corePoolSize:核心线程池的大小, 标准池,(公交车正常做17个人)
    ▪ maximumPoolSize:线程池能创建线程的最大个数 (公交车早晚高峰最大能做57个人)
    ▪ keepAliveTime:空闲线程存活时间 (临时加了13个座位,多长时间把13个座位撤掉)
    ▪ unit:时间单位,为keepAliveTime指定时间单位
    ▪ workQueue:阻塞队列,用于保存任务的阻塞队列 (如果没有要自己维护共享变量,需要有等待和唤醒的过程,就有了GUC)
    ▪ threadFactory:创建线程的工程类
    ▪ handler:饱和策略(拒绝策略)

    五、拒绝策略

    ▪ ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
    ▪ ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    ▪ ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ▪ ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
  • 相关阅读:
    k8s 新加节点
    /etc/bashrc
    k8s 连接harbor 的私有仓库的两种方法 一种是secret 绑定到sa serviceaccount 账号下 一种是需要绑定到 imagePullSecrets:
    pip install --upgrade urllib3==1.25.2
    mysql skip-grant-tables 后要多次重启 和验证登录检查确认密码生效
    k8s 传参给docker env command、args和dockerfile中的entrypoint、cmd之间的关系
    kubectl -n ingress-nginx exec nginx-ingress-controller-78bd49949c-t22bl -- cat /etc/nginx/nginx.conf
    更新Alpine Linux源 sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories apk add xxx
    ingress nginx https配置
    Ingress-nginx 部署使用
  • 原文地址:https://www.cnblogs.com/kongxiangqun/p/16178737.html
Copyright © 2020-2023  润新知