1.定时器
1.1 定时器是什么
定时器也是软件开发中的一个重要组件.
类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定好的代码.
在标准库里,也是有现成的定时器的实现的
import java.util.Timer;
import java.util.TimerTask;
// 定时器
public class Demo25 {
public static void main(String[] args) {
Timer timer = new Timer();
// 给定时器安排了一个任务, 预定在 xxx 时间去执行.
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("3000");
}
}, 3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("2000");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("1000");
}
}, 1000);
System.out.println("程序启动!");
}
}
1.2 如何实现定时器
mport java.util.PriorityQueue;
// 通过这个类, 描述了一个任务
class MyTimerTask implements Comparable<MyTimerTask> {
// 要有一个要执行的任务
private Runnable runnable;
// 还要有一个执行任务的时间
private long time;
// 此处的 delay 就是 schedule 方法传入的 "相对时间"
public MyTimerTask(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
@Override
public int compareTo(MyTimerTask o) {
// 这样的写法, 就是让队首元素是最小时间的值
// 到底是谁 - 谁, 不要背!! 你可以试试!!
return (int) (this.time - o.time);
// 如果是想让队首元素是最大时间的值
// return o.time - this.time;
}
public long getTime() {
return time;
}
public Runnable getRunnable() {
return runnable;
}
}
// 咱们自己搞的定时器
class MyTimer {
// 使用一个数据结构, 保存所有要安排的任务.
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
// 使用这个对象作为锁对象.
private Object locker = new Object();
public void schedule(Runnable runnable, long delay) {
synchronized (locker) {
queue.offer(new MyTimerTask(runnable, delay));
locker.notify();
}
}
// 搞个扫描线程.
public MyTimer() {
// 创建一个扫描线程
Thread t = new Thread(() -> {
// 扫描线程, 需要不停的扫描队首元素, 看是否是到达时间.
while (true) {
try {
synchronized (locker) {
// 不要使用 if 作为 wait 的判定条件, 应该使用 while
// 使用 while 的目的是为了在 wait 被唤醒的时候, 再次确认一下条件.
while (queue.isEmpty()) {
// 使用 wait 进行等待.
// 这里的 wait, 需要由另外的线程唤醒.
// 添加了新的任务, 就应该唤醒.
locker.wait();
}
MyTimerTask task = queue.peek();
// 比较一下看当前的队首元素是否可以执行了.
long curTime = System.currentTimeMillis();
if (curTime >= task.getTime()) {
// 当前时间已经达到了任务时间, 就可以执行任务了
task.getRunnable().run();
// 任务执行完了, 就可以从队列中删除了.
queue.poll();
} else {
// 当前时间还没到任务时间, 暂时不执行任务.
// 暂时先啥都不干, 等待下一轮的循环判定了.
locker.wait(task.getTime() - curTime);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Demo26 {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("3000");
}
}, 3000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("2000");
}
}, 2000);
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("1000");
}
}, 1000);
System.out.println("程序开始执行");
}
}
2.线程池
线程诞生的意义,是因为进程的创建/销毁, 太重量了(比较慢)有对比,才有伤害,和进程比,线程,是更快了,但是如果进一步提高创建销毁的频率, 线程的开销也不能忽视了!
两种典型的办法,进一步提高效率:
1.协程(轻量级线程)
相比于线程,把系统调度的过程,给省略了.(程序猿手工调度当下,一种比较流行的并发编程的手段. 但是在 Java 圈子里,协程还不够流行.
2.线程池
线程池最大的好处就是减少每次启动、销毁线程的损耗。
2.1 线程池的使用
2.2 线程池的实现
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class MyThreadPool {
// 任务队列
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
// 通过这个方法, 把任务添加到队列中
public void submit(Runnable runnable) throws InterruptedException {
// 此处咱们的拒绝策略, 相当于是第五种策略了. 阻塞等待~~ (这是下策)
queue.put(runnable);
}
public MyThreadPool(int n) {
// 创建出 n 个线程, 负责执行上述队列中的任务.
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
// 让这个线程, 从队列中消费任务, 并进行执行.
try {
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
}
public class Demo28 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool = new MyThreadPool(4);
for (int i = 0; i < 1000; i++) {
int id = i;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("执行任务: " + id);
}
});
}
}
}
主线:
线程概念 -> Thread 用法 ->线程安全问题 ->wait notify -> 线程案例