欢迎来到 Kong Xiangqun 的博客

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:由调用线程处理该任务

 

posted @ 2022-04-25 21:36  kongxiangqun20220317  阅读(26)  评论(0编辑  收藏  举报