多线程五

43 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

复习

1.单列模式
    1)加锁
    2)双重if
    3)volatile
2.阻塞队列 => 生产者消费者模型
    1)线程安全
    2)带有阻塞

定时器

在一段时间之后被唤醒执行一定的操作之前学到的sleepjoin也是系统内部的定时器

定时器使用

这边用到的是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);
    }
}