目录
1.定时器
定时器是软件开发中一个重要的主键,类似于一个“闹钟”,达到一个设定的时间之后, 就执行某个指定 好的代码。
比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连。比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除). 类似于这样的场景就需要用到定时器。
1.1 标准库中的定时器
标准库中提供了一个Timer 类。Timer 类中的核心方法为 schedule。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);
1.2 实现定时器
(1)一个带优先级的阻塞队列.因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的 . 使用带 优先级的队列就可以高效的把这个 delay 最小的任务找出来。(2) 队列中的每个元素是一个 Task 对象(3)Task 中带有一个时间属性 , 队首元素就是即将执行的任务(4)同时有一个 worker 线程一直扫描队首元素 , 看队首元素是否需要执行
1) Timer 类提供的核心接口为 schedule, 用于注册一个任务, 并指定这个任务多长时间后执行。
class MyTimer {
public void schedule(Runnable command, long after) {
//
}
}
2)Task 类用于描述一个任务(作为 Timer 的内部类).。里面包含一个 Runnable 对象和一个
time(毫秒时间戳)。这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口
/创建一个类表示一个任务
class MyTask implements Comparable<MyTask> {
//任务具体要什么
private Runnable runnable;
//任务具体开始执行时间,保存任务直接的毫秒时间戳
private long time;
//after 是一个时间间隔,不是绝对的时间戳
public MyTask(Runnable runnable,long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
//谁小谁排在前面
return (int) (this.time - o.time);
}
}
3)Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象,通过 schedule 来往队列中插入一个个 Task 对象。
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<MyTask>();
public void schedule(Runnable runnable,long delay){
MyTask task = new MyTask(runnable,delay);
queue.put(task);
}
}
4)Timer 类中存在一个 worker 线程, 一直不停的扫描队首元素, 看看是否能执行这个任务。
class Timer {
// ... 前面的代码不变
public MyTimer() {
Thread t = new Thread(() ->{
while (true) {
try {
//先取出队首元素
MyTask task = queue.take();
//比较当前的时间和任务开始执行的时间,看看任务时间到了没有
long curTime = System.currentTimeMillis();
if (curTime < task.getTime()) {
//时间还没到,把任务放回队列里
queue.put(task);
} else {
//时间到了,开始执行任务
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
但是当前这个代码中存在一个严重的问题, 就是 while (true) 转的太快了, 造成了无意义的 CPU 浪费。
比如第一个任务设定的是 1 min 之后执行某个逻辑. 但是这里的 while (true) 会导致每秒钟访问队 首元素几万次. 而当前距离任务执行的时间还有很久。所以需要引入wait
class Timer {
// 存在的意义是避免 worker 线程出现忙等的情况
private Object loker = new Object();
}
修改Worker 的 run 方法, 引入 wait, 等待一定的时间。
public MyTimer() {
Thread t = new Thread(() ->{
while (true) {
try {
//先取出队首元素
MyTask task = queue.take();
//比较当前的时间和任务开始执行的时间,看看任务时间到了没有
long curTime = System.currentTimeMillis();
if (curTime < task.getTime()) {
//时间还没到,把任务放回队列里
queue.put(task);
//指定等待时间,任务的开始时间减当前时间
synchronized (loker) {
loker.wait(task.getTime() - curTime);
}
} else {
//时间到了,开始执行任务
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
6) 修改 Timer 的 schedule 方法, 每次有新任务到来的时候唤醒一下 worker 线程。 (因为新插入的任务可能 是需要马上执行的)。
ublic void schedule(Runnable runnable,long delay){
MyTask task = new MyTask(runnable,delay);
queue.put(task);
//每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行
synchronized (loker) {
loker.notify();
}
}
1.3 完成代码
import java.util.concurrent.PriorityBlockingQueue;
//创建一个类表示一个任务
class MyTask implements Comparable<MyTask> {
//任务具体要什么
private Runnable runnable;
//任务具体开始执行时间,保存任务直接的毫秒时间戳
private long time;
//after 是一个时间间隔,不是绝对的时间戳
public MyTask(Runnable runnable,long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
class MyTimer {
//定时器内部要能够存放多个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<MyTask>();
public void schedule(Runnable runnable,long delay){
MyTask task = new MyTask(runnable,delay);
queue.put(task);
//每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行
synchronized (loker) {
loker.notify();
}
}
private Object loker = new Object();
public MyTimer() {
Thread t = new Thread(() ->{
while (true) {
try {
//先取出队首元素
MyTask task = queue.take();
//比较当前的时间和任务开始执行的时间,看看任务时间到了没有
long curTime = System.currentTimeMillis();
if (curTime < task.getTime()) {
//时间还没到,把任务放回队列里
queue.put(task);
//指定等待时间,任务的开始时间减当前时间
synchronized (loker) {
loker.wait(task.getTime() - curTime);
}
} else {
//时间到了,开始执行任务
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
/**
* 定时器存放任务
*/
public class Demo2 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("开始执行任务");
}
},3000);
System.out.println("main");
}
}
执行效果
2.线程池
2.1 标准库中的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
newCachedThreadPool: 创建线程数目动态增长的线程池 .newSingleThreadExecutor: 创建只包含单个线程的线程池 .newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令, 是进阶版 Timer。
Executors 本质上是 ThreadPoolExecutor 类的封装。
Executors 本质上是 ThreadPoolExecutor 类的封装。
2.2 实现线程池
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool {
// 1. 描述一个任务. 直接使用 Runnable, 不需要额外创建类了.
// 2. 使用一个数据结构来组织若干个任务.
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// 3. 描述一个线程, 工作线程的功能就是从任务队列中取任务并执行.
static class Worker extends Thread {
// 当前线程池中有若干个 Worker 线程 这些 线程内部 都持有了上述的任务队列.
private BlockingQueue<Runnable> queue = null;
public Worker(BlockingQueue<Runnable> queue) {
this.queue = queue;
}
@Override
public void run() {
// 就需要能够拿到上面的队列
while (true) {
try {
// 循环的去获取任务队列中的任务.
// 这里如果队列为空, 就直接阻塞. 如果队列非空, 就获取到里面的内容~~
Runnable runnable = queue.take();
// 获取到之后, 就执行任务.
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 4. 创建一个数据结构来组织若干个线程.
private List<Thread> workers = new ArrayList<>();
public MyThreadPool(int n) {
// 在构造方法中, 创建出若干个线程, 放到上述的数组中.
for (int i = 0; i < n; i++) {
Worker worker = new Worker(queue);
worker.start();
workers.add(worker);
}
}
// 5. 创建一个方法, 能够允许程序猿来放任务到线程池中.
public void submit(Runnable runnable) {
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Demo26 {
public static void main(String[] args) {
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 100; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello threadpool");
}
});
}
}
}