1.定时器
就像名字一样,功能是定时
可以通过引用Timer库,调用定时器函数
public static void main(String[] args) {
Timer timer = new Timer();//设置定时器
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("该起床了");
}
},1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("该喝水了");
}
},1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("该刷牙了");
}
},10000);
}
schedule(对象,时间) 对象里会自动生成run()函数,在run()函数里写想实现的功能
下面,我们来手动实现下定时器
首先,我们可以看到schedule由两部分组成,分别是对象和时间,于是我们就想着先实现一个类来描述要具体实现的功能以及执行的时间
1.一个类来描述要具体实现的功能以及执行的时间
由于schedule要一个对象,所以我们利用Runnable方法来实现获取一个类,从而在MyTask类中定义一个runnable对象,由于schedule要一个时间来终止,所以我们定义一个时间对象,然后在构造类的函数中将两个变量传进去,便于MyTimer来使用
private Runnable runnable;
private long delay;
public Runnable getRunnable() {//让MyTimer获取对象
return runnable;
}
public long getDelay() {//让MyTimer获取时间
return delay;
}
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.delay = time + System.currentTimeMillis();//加上现在的时间戳,以便其运行
}
2.另一个类负责利用多线程来实现等待和通知的功能
1.定义一个容器来装定时器的很多对象,使用优先级队列,这样不仅做到了存储功能也做到了按时间先后来进行定时器的操作
private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
2.创建线程来进行定时器的操作
public MyTimer() {//构造类方法
Thread thread = new Thread(() ->{
while(true){
try {
MyTask myTask = queue.take();//从优先级队列中取出任务
long time = System.currentTimeMillis() ;
if(time >= myTask.getTime()){
//时间到了,可以执行任务了
myTask.getRunnable().run();
}else{
//如果没有到时间,就放回队列
queue.put(myTask);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
thread.start();
}
优化思路
1.解决忙等
2.解决唤醒
3.解决wait和notify时间不匹配问题
4.考虑极端情况,当队列中只有一个数据时会发生死锁
但是这样虽然能得到运行结果,可是每次等待时间然后一次又一次的while循环,显然调用了不必要的资源,我们将其称做忙等,
于是我们尝试在这段代码中加入wait让wait来等待一段时间,以便节约资源
于是我们在后面 加上了一个wait
但是,如图,假设现在是18:52,先进行了等待1个小时,才将19:00的时间加入阻塞队列,导致了19:00 时间的无法运行,所以我们需要在添加时重新唤醒一下wait,所以我们在插入数据的时候写一个notifyall()来通知wait来更改waitTime
考虑到cpu的调度是随机的,可能在前面插入时就直接调度到了等待,导致队列里有时间被掠过了,所以属于提前唤醒了,也会导致时间错过,就是说要保证notify唤醒的和wait之前的时间一致,所以要将synchronized提到获取时间那里去
所以我们虽然解决了及时处理任务问题,但是确造成了更严重的问题,所以我们两个取其中,换一个方法,将synchronized重新移动到里面,使用另一种方法,使用后台进程
当synchronized提到前面是,还是有不及时的问题,所以我们写一个后台线程,每隔几毫秒去检查,确保不会错过时间即可
2.线程池
1.什么是线程池
就是将很多线程放在一起,随用随到
2.为什么要用线程池
避免要用到线程的时候还要创建和销毁,因此浪费资源
3.怎么用
1.可以使用jdk里自带的类(面试要考,记得背)
4.初步实现下线程池
1.首先先创建一个阻塞队列,用来存放数据
2.实现submit方法,将数据放入阻塞队列
3.再在构造方法中使用传进来的容量来创建线程
public class ThreadPool {
//定义一个阻塞队列来组织任务
BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>(100);
public ThreadPool(int theradNum) {
if (theradNum <= 0) {
throw new IllegalArgumentException("线程数量必须大于0");
}
for (int i = 0; i < theradNum; i++) {
Thread th = new Thread(()->{
while(true){
try {
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
th.start();
}
}
public void submit(Runnable runnable) throws InterruptedException {
if(runnable == null){
throw new IllegalArgumentException("任务不能为空");
}
queue.put(runnable);
}
}
2.自己利用系统来创建线程池