多线程案例【二】

定时器

定时器像是一个闹钟,在一定时间之后,被唤醒并执行某个之前设定好的任务。

之前学习的 join(指定超时时间)
sleep(休眠指定时间)
都是基于系统内部的定时器,来实现的。

标准库中的定时器

标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule 。
schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后执行 (单位为毫秒)

public class Demo23 {
    public static void main(String[] args) {
        Timer timer =new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello timer");
            }
        },3000);
        System.out.println("main");
    }
}

执行效果
在这里插入图片描述

实现定时器

Timer内部需要什么东西?

1)描述任务
创建一个专门的类来表示一个定时器中的任务(TimerTask)


//创建一个类,表示一个任务
class MyTask{
    //任务具体要干什么
    private Runnable runnable;
    //任务具体什么时候干,保存任务要执行的毫秒级时间戳
    private long time;

    public MyTask(Runnable runnable, long after) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis()+after;
    }
    public  void run(){
        runnable.run();

    }

}

2)组织任务(使用一定的数据结构把一些任务给放到一起)
在这里插入图片描述

class MyTimer{
    //定时器内部要能存放多个任务
    private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable,long delay){
        MyTask task=new MyTask(runnable, delay);
        queue.put(task);
    }
}

3)执行时间到了的任务
需要先执行时间在靠前的任务
就需要一个线程,不停的去检查当前优先级队列的队首元素,看看说当前最靠前的这个任务是不是时间到了。

//提供一个MyTimer的构造方法
    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();
    }

上述代码存在严重缺陷!!!
第一个缺陷:MyTask没有指定比较规则在这里插入图片描述
MyTask这个类的比较规则,并不是默认就存在的,这个需要手动指定,按照时间大小来比较的。
Java标准库中的集合类,很多都有一定的约束限制的,不是随便拿个类都能放到这些集合类里面去的

第二个缺陷
在这里插入图片描述
可以基于wait这样的机制来实现
wait有个版本,指定等待时间(不需要notify,时间到了自然唤醒)。
计算出当前时间和任务目标之间 的时间差,就等待这么长时间。

那么既然是指定一个等待时间,,为什么不直接使用sleep呢?而是要用wait?
sleep不能被中途唤醒
wait能够被中途唤醒
在等待过程中,可能要插入新的任务,新的任务是可能出现在之前所有任务的最前面的,在schedule操作中,就需要加上一个notify操作。

因此最终的代码是:

//创建一个类,表示一个任务
class MyTask implements Comparable<MyTask>{
    //任务具体要干什么
    private Runnable runnable;
    //任务具体什么时候干,保存任务要执行的毫秒级时间戳
    private long time;

    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<>();
    public void schedule(Runnable runnable,long delay){
        MyTask task=new MyTask(runnable, delay);
        queue.put(task);
        //每次任务插入成功之后,都唤醒一下扫描线程,让线程重新检查一下队首元素的任务是否时间到要执行
        synchronized (locker){
            locker.notify();
        }
    }

      private Object locker=new Object();

    //提供一个MyTimer的构造方法
    public MyTimer(){
        Thread t=new Thread(() ->{
            while(true){
                try {
                    //先取出队首元素
                    MyTask task=queue.take();
                    //在比较一下看看当前这个任务时间到了没
                    long curTime=System.currentTimeMillis();
                    if(curTime < task.getTime()){
                        //时间没到,把任务在放到队列中
                        //指定一个等待时间
                        synchronized (locker){
                            locker.wait(task.getTime()-curTime);
                        }
                        queue.put(task);
                    }else{
                        //时间到了,执行任务
                        task.run();

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        t.start();
    }
}

public class Demo24 {
    public static void main(String[] args) {
        MyTimer timer=new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello timer");
            }
        },3000);
        System.out.println("hello main");

    }
}

线程池

进程,比较重,频繁创建销毁,开销大,解决方案:进程池或线程。
线程,虽然比进程轻了,但是如果创建销毁的频率进一步增加,仍然会发现开销还是有的,解决方案:线程池或协程。

把线程提前创建好,放到池子里,后面需要使用线程,直接从池子里取,就不必从系统这边申请了。线程用完,也不是还给系统,而是放回池子里,以备下次再用。
这回创建销毁过程,速度就更快了。

为什么认为线程放到池子里,就比从系统这边申请释放更快呢??
在这里插入图片描述

Java标准库的线程池

在这里插入图片描述

有一个程序,这个程序要并发的 / 多线程的来完成一些任务,如果使用线程池的话,这里的线程数设为多少合适?
正确的做法:要通过性能测试的方式,找到合适的值
例如:写一个服务器程序,服务器里通过线程池,多线程的处理用户请求。
就可以对这个服务器进行性能测试,比如构造一些请求,发送给服务器,要测试性能,这里的请求就需要构造很多,比如每秒发送500 / 1000/2000…,根据实际的业务场景,构造一个合适的值。
根据这里不同的线程池的线程数,来观察,程序处理任务的速度,程序持有的CPU的占用率。
当线程数多了,整体的速度会快,CPU占用率也会高
当线程数少了,整体的速度会慢,CPU占用率也会下降。
需要找到一个让程序速度能接受,并且CPU占用率也合理这样的平衡点。
不同类型的程序,因为单个任务,里面的CPU上计算的时间和阻塞的时间分布是不同的。因此,只说一个数字是不靠谱的。

搞多线程,就是为了让程序跑的更快,那为什么考虑不让CPU占用率太高?
对于线上服务器来说,要留有一定的冗余,随时应对一些可能的突发情况(例如,请求突然暴涨)
如果本身已经把CPU快占完了,这时候突然来一波请求的峰值,此时服务器可能直接就挂了。

标准库中还提供了一个简化版本的线程池
Executors
本质是针对ThreadPollExecutor进行封装,提供了一些默认参数。

public class Demo25 {
    public static void main(String[] args) {
        //创建一个固定线程数目的线程池,参数指定了线程个数
        ExecutorService pool=Executors.newFixedThreadPool(10);
        //创建一个自动扩容的线程池,会根据任务自动进行扩容
        //Executors.newCachedThreadPool();
        //创建一个只有一个线程的线程池
       // Executors.newSingleThreadExecutor();
        //创建一个带有定时器功能的线程池,类似于Timer
       // Executors.newScheduledThreadPool();
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello threadpool");
            }
        });
    }
}

实现线程池

线程池中有什么?
1.先能够描述任务(直接使用Runnable)
2.需要组织任务(直接使用BlockingQueue)
3.能够描述工作线程
4.还需要组织这些线程
5.需要往线程池里添加任务

class MyThreadPool{
    //1.描述一个任务,直接使用Runnable,不需要额外创建类了
    //2.使用一个数据结构来组织若干个任务
    private BlockingDeque<Runnable> queue=new LinkedBlockingDeque<>();
    //3.描述一个线程,工作线程的功能就是从任务队列中取任务并执行
    static class Worker  extends  Thread{
        //当前线程池中有若干个worker线程,这些线程内部,都持有了上述的任务队列
        private  BlockingDeque<Runnable> queue=null;
        public Worker(BlockingDeque<Runnable> queue){
            this.queue=queue;

        }
        @Override
        public void run() {
            while(true){
                //就需要能够拿到上面的队列
                try {
                    //循环的获取任务队列中的任务
                    //这里如果队列为空,就直接阻塞,如果队列不为空,就获取到里面的内容
                    Runnable runnable=queue.take();
                    //获取到之后,就执行任务
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //创建哪一个数据结构来组织若干个线程
    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);
        }

    }
    //创建一个方法,能够让程序员来放任务到线程池
    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 thraedpool");
                }
            });
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值