22、线程池
一、为什么需要线程池
任务其实就是写的代码,提交任务就是代码逻辑,是需要线程来运行的
从线程池里面拿取数据,里面有具体的执行过程
核心线程池:开始无法预估系统, 假如预设值30个,不管有没有用户的连接,就保持30个,这就是核心线程池的数量
根据业务需求,并发访问量决定就好
阻塞队列:只有30个线程,但是同一时刻提交了300个任务,不可能同时执行300个,先执行30个,剩下的270个放在任务队列中
线程池:比如有个水缸,平常只放半缸水就好,它里面有一个max最大容量,最大容量如果没满就创建线程执行任务
二、线程池的分类
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和TERMINATED,但是在从RUNNING到TERMINATED这个状态过度的时候
会有中间3种状态,第一种调用shutdown()时会切换到SHUTDOWN,第二种调用shutdown Now() 时切换到STOP,
最终会进行一个回收的工作,SHUTDOWN回收时当前正在执行的任务会执行完(阻塞队列为空,线程池中的工作线程数量为0)
进入到TIDYING
STOP队列里有任务也会直接干掉,不是等待执行完再干掉
之前我们说不管哪个分类都调用ThreadPoolExecutor
四、ThreadPoolExecutor
构造方法
五、拒绝策略