欢迎关注个人主页:逸狼
创造不易,可以点点赞吗~
如有错误,欢迎指出~
目录
实例3:线程池
参数解释
核心线程数, 最大线程数
允许空闲的最大时间 ,时间单位
任务队列(阻塞队列)
线程工厂=>工厂设计模式
拒绝策略
使用举例
模拟实现一个线程池(固定线程数目的线程池)
实例4:定时器
使用举例
定时器的模拟实现
选定数据结构
线程安全问题
不使用sleep
实例3:线程池
线程池 就是把线程提前从系统中申请好,放到一个地方,后面需要使用线程的时候,直接从这个地方来取,而不是从系统中重新申请,线程用完之后,也是会还回到刚才的地方.主要是解决随着业务上对于性能要求越来越高,线程创建开销的频次越来越多的 问题,
参数解释
核心线程数, 最大线程数
允许空闲的最大时间 ,时间单位
任务队列(阻塞队列)
线程工厂=>工厂设计模式
拒绝策略
使用举例
ThreadPoolExecutor 是封装前的 ->定制性更强,用起来更麻烦
Executors 是封装过的->定制性比较弱,用起来简单
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Demo30 {public static void main(String[] args) throws InterruptedException {ExecutorService service= Executors.newFixedThreadPool(4);//代表线程池中有4个线程for (int i = 0; i < 100; i++) {int id =i;//让id成为事实final,让lambda捕获service.submit(()->{Thread current = Thread.currentThread();//当前线程System.out.println("hello thread"+ id +","+ current.getName());});}//最好不要立即就终止, 可能任务还没执行完,线程就终止了Thread.sleep(2000);//把线程池里的线程都 终止掉service.shutdown();System.out.println("程序退出");}
}
加上 service.shutdown(),让前台线程强制结束
如何指定线程池中的线程个数
模拟实现一个线程池(固定线程数目的线程池)
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;class MyThreadPool{private BlockingQueue<Runnable> queue =new ArrayBlockingQueue<>(1000);//此处n表示创建几个线程public MyThreadPool(int n){//先创建n个线程for (int i = 0; i < n; i++) {Thread t =new Thread(()->{//循环的从队列中 取任务while(true){try {Runnable runnable = queue.take();runnable.run();//执行任务} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}}//添加任务public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}
}public class Demo31_MyPool {public static void main(String[] args) throws InterruptedException {//测试代码MyThreadPool pool=new MyThreadPool(4);for (int i = 0; i < 1000; i++) {int id=i;pool.submit(()->{System.out.println("执行任务"+ id +"," + Thread.currentThread().getName());});//lambda对应的是"函数式接口", Runnable 也是同样符合这样的要求的}}
}
实例4:定时器
定时器相当于"闹钟",网络通信中,经常需要设定一个"超时时间",Java标准库中也提供了定时器实现
定时器在后端开发中特别重要和常用,和"阻塞队列" 类似,也会有专门的服务器(用来在分布式系统中 实现定时器的效果)
如果定的任务时间都是一样的值,接下来任务的执行顺序可能是串行的,也可能是并发的(取决于定时器的具体实现)
使用举例
TimerTask本质上就是Runnable的进一步实现
import java.util.Timer;
import java.util.TimerTask;public class Demo31 {public static void main(String[] args) {Timer timer=new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello3");}},3000);//表示延时3秒钟 打印hello3timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello2");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello1");}},1000);System.out.println("程序开始执行");}
}
定时器的模拟实现
定时器的实现步骤:
- 创建类,描述一个要执行的任务(任务的内容,任务的时间)
- 管理多个任务,通过一定的数据结构,把多个任务存起来
- 有专门的线程,执行这里面的任务
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;class MyTimerTask implements Comparable<MyTimerTask> {private Runnable runnable;// 此处这里的 time, 通过毫秒时间戳, 表示这个任务具体啥时候执行.private long time;public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}public long getTime() {return time;}@Overridepublic int compareTo(MyTimerTask o) {// 此处这里的 - 的顺序, 就决定了这里是大堆还是小堆.// 此处需要小堆.// 这里是谁减谁, 不要背. 可以先写成一种顺序, 试试.return (int) (this.time - o.time);// return (int) (o.time - this.time);}
}class MyTimer {private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();private Object locker = new Object();public MyTimer() {// 创建线程, 负责执行上述队列中的内容Thread t = new Thread(() -> {try {while (true) {synchronized (locker) {while (queue.isEmpty()) {locker.wait();}MyTimerTask current = queue.peek();// 比如, 当前时间是 10:30, 任务时间是 12:00, 不应该执行.// 如果当前时间是 10:30, 任务时间是 10:29, 应该执行if (System.currentTimeMillis() >= current.getTime()) {// 要执行任务current.run();// 把执行过的任务, 从队列中删除.queue.poll();} else {// 先不执行任务locker.wait(current.getTime() - System.currentTimeMillis());// Thread.sleep(current.getTime() - System.currentTimeMillis());}}}} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();}public void schedule(Runnable runnable, long delay) {synchronized (locker) {MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);queue.offer(myTimerTask);locker.notify();}}
}
选定数据结构
按照时间来执行任务,只要能够确定所有任务中时间最小的任务,判定其是否到达执行时间即可(其他时间的任务必定排在 时间最小任务后面),所以使用优先级队列是最好的选择
为啥不使用BlockingQueue阻塞队列作为实现定时器的数据结构?
- 阻塞队列里的take里也有一把锁,容易出现死锁情况
- 代码的复杂程度会增加
对于所有的修改操作都要加上锁
线程安全问题
通过对进队列和出队列进行加锁
使用wait操作,避免出现 "线程饿死" 这种情况
不使用sleep
为啥使用wait,不使用sleep让线程阻塞?
业界实现定时器除了基于优先级队列的方式之外,还有一种典型的实现方式,"时间轮"(也是一种巧妙设计的数据结构)