本文已参与「新人创作礼」活动,一起开启掘金创作之路。
复习
1.单列模式
1)加锁
2)双重if
3)volatile
2.阻塞队列 => 生产者消费者模型
1)线程安全
2)带有阻塞
定时器
在一段时间之后被唤醒执行一定的操作之前学到的sleep和join也是系统内部的定时器
定时器使用
这边用到的是Timer类的schedule方法,这个方法有两个参数,一个是执行的任务,一个是等待的时间,
如果时间超出了指定的时间,就不会继续等待下去。
public class Demo22 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello Timer");
}
} , 2000);
}
}
这个里面的TimerTask其实也是一个Runnable。运行之后你会发现,程序不会自己结束。这个是为什么呢?
后面会介绍
自己实现TimerTask
实现的时候主要考虑的有两个内容,一个是执行的时间,一个是执行的任务,所以我们自己创建的Task要有这两个东西
我们的Time要管理多个任务,所以我们要用到一个数据结构来帮忙管理一下,那我们用什么呢?
链表?
不行,这个不能排序,如果有个任务来的早,但是要执行是一年后,
一个任务来的晚,但是执行是一秒后,我们无法辨别。
数组?
可以是可以,但是要排序,任务的插入和删除,又要重新排,不灵活。
.......
最后决定使用堆来解决问题。
所以Time要创建一个堆
那么代码就可以写好了。
Task代码
class MyTask implements Comparable<MyTask>{
private Runnable runnable;
private long time;
public MyTask(Runnable run ,long after){
runnable = run;
time = System.currentTimeMillis() + after;
}
public void run(){
runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int)(this.time - o.time);
}
public long getTime(){
return this.time;
}
}
这个还是有一个重要的点的。就是实现了Comparable接口,为什么呢?那就是因为我们的Time使用到了堆,需要这个比较器来比较他们之间的先后顺序。
Time代码
class MyTime{
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
private Object object = new Object();
public void schedule(Runnable run , long time){
MyTask myTask = new MyTask(run , time);
queue.put(myTask);
synchronized(object){
object.notify();
}
}
public MyTime(){
Thread t = new Thread(()->{
while(true){
try {
MyTask cur = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < cur.getTime()){
queue.put(cur);
synchronized (object){
object.wait(cur.getTime() - curTime);
}
}else{
cur.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
这边有几个重要的点
1.wait:为什么这边要上锁呢?因为这边是一个重要的优化,你想想看,如果没有这个锁,我们的程序是不是一直在执行读取,对时间,时间没到就再插入,然后再读取,对时间,时间没到就再插入。以此往复,这就消耗了太多的资源,所以我们要上锁用wait
2.为什么用wait,我们之前学习到的sleep可以用吗?感觉也是可以的呀,那为什么不用呢?原因就是sleep不能被唤醒,但是wait可以用notify来唤醒。
线程池
解决线程频繁调用销毁
就是提前创建线程,线程要创建的时候就往池子里面拿,
销毁线程的时候,也不是销毁,就是把线程放回池子里面
使用线程池
创建一个固定线程数目的线程池
ExecutorService pool1 = Executors.newFixedThreadPool(10);
创建一个自动扩容的线程池,会根据任务量来扩建
ExecutorService pool2 = Executors.newCachedThreadPool();
创建一个只有一个线程的线程池
ExecutorService pool3 = Executors.newSingleThreadExecutor();
创建一个带有定时器功能的线程池,类似于Timer
ExecutorService pool4 = Executors.newScheduledThreadPool();
使用
pool1.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello pool");
}
});
线程池实现
实现线程池之前我们要了解线程池里面有什么
1.能够描述任务
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
2.需要组织任务
static class work extends Thread{
private BlockingQueue<Runnable> queue = null;
public work(BlockingQueue<Runnable> queue){
this.queue = queue;
}
@Override
public void run() {
while(true){
try {
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
3.能够描述工作线程
private List<Thread> works = new ArrayList<>();
4.组织这些线程
public MyThreadPool(int n){
for(int i = 0;i<n;i++){
work work = new work(queue);
work.start();
works.add(work);
}
}
5.往线程池里面添加任务
public void submit(Runnable runnable){
try {
queue.put(runnable);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}