javaEE初阶————多线程初阶(5)

本期是多线程初阶的最后一篇文章了,下一篇就是多线程进阶的文章了,大家加油!

一,模拟实现线程池

我们上期说过线程池类似一个数组,我们有任务就放到线程池中,让线程池帮助我们完成任务,我们该如何实现线程池呢,我们来想一想线程的构造方法,第一个参数是核心线程数,第二个是最大线程数,这个我们不考虑,第三个是最大空闲时间,第四个事枚举的时间类型,第五个是阻塞队列,完了是工厂模式,之后是拒绝策略;

我们来模拟一下;

java 复制代码
package Demo1;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class MyThreadPool {
    private BlockingQueue<Runnable> queue = null;

    public MyThreadPool(int n){
        queue = new ArrayBlockingQueue<>(n);

        for (int i = 0; i < n; i++) {
            Thread t  = new Thread(()->{
                while(true){
                    try {
                        queue.take().run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
        }
    }

    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

我们先来看这段代码,我们先起了类名MyThreadPool,在类中先弄了一个未初始化的阻塞队列,在创建构造方法,由传入的数字来创建阻塞队列的容量也就是线程池中有几个线程,我们根据传入的数字n来创建n个线程,让n个线程循环往复的到阻塞队列中去哪任务,还有submit方法,我们让将拿到的任务添加到阻塞队列中去,我们来测试代码;

java 复制代码
public class test {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool = new MyThreadPool(4);

        for (int i = 0; i < 50; i++) {
            int id = i;
            pool.submit(()->{
            System.out.println(id + " and " + 123456);
        });
        }
    }
}

我们创建了50个任务,让线程池来完成,我们看看运行结果;

我们看到线程随机执行了我们的任务;

最后是没有结束的啊,没有正常的返回值,这是因为我new thread创建的线程是前台线程,那几个线程还在阻塞着等待任务呢,我们想要结束应该把他们设置为后台线程才行;

---------------------------------------------------------------------------------------------------------------------------------

二,定时器详解

定时器是什么,定时器也是软件开发中的一个重要组件,它有什么用的,大家使用浏览器的时候有没有发生过连接断开或者未响应之类的情况,其实这就是定时器在发挥,如果没有定时器的话就会一直请求,但是没准这次的请求就不会得到响应,为了不发生无休止的等待,我们就使用定时器来中断这次请求,既然响应不了那就直接结束;

那么我们如何使用定时器呢?

标准库中提供了Timer类,Timer类中包含一个schedule方法,我们使用schedule方法来使用定时器功能,schedule方法中有两个参数,一个是需要执行的任务;

schedule(new TimerTask(抽象类,继承了Runnable接口),时间);

我们来用代码展示一下;

java 复制代码
import java.util.Timer;
import java.util.TimerTask;

public class test {
    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1s后   任务1");
            }
        },1000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("3s后   任务2");
            }
        },3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("5s后   任务3");
            }
        },5000);

        Thread.sleep(7000);
        timer.cancel();
    }
}

我们给Timer三个任务让他在规定时间段运行,并最终终止Timer线程,我们来看运行效果;

另外,Timer其实已经过时,

我推荐使用更强的scheduledExecutorService,Timer是单线程的,但是scheduledExecutorService是线程池,功能更强,我们来说说用法;

1,延迟执行任务

schedule(Runnable command,long delay,TimeUnit unit)

第一个参数是任务,就是接口就行,第二个参数是时间,意思是多长时间执行任务,达到延迟效果,第三个参数是时间类型,也就是枚举类型;

我们来上代码;

这是第三个参数枚举类型,提供了很多的枚举;

java 复制代码
public class Test2 {
    public static void main(String[] args) {
        ScheduledExecutorService sr = Executors.newScheduledThreadPool(1);
        sr.schedule(()->{
            System.out.println("2秒后    打印");
        },2, TimeUnit.SECONDS);

        sr.schedule(()->{
            System.out.println("4秒后    打印");
        },4,TimeUnit.SECONDS);

        sr.shutdown();
    }
}

我们使用shutdown方法来关闭调度器,来看运行结果;

程序正常结束了;

2,定期执行任务

scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)

第一个参数还是接口任务,第二个参数是初始延迟时间,第三个参数是任务执行的时间间隔,最后一个参数还是时间的枚举类型;

我们来上代码;

java 复制代码
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(()->{
            System.out.println("任务执行了");
        },1,2, TimeUnit.SECONDS);

        Thread.sleep(10000);
        service.shutdown();
    }
}

还有一种固定时间延迟执行任务,跟这个是基本一样的,

schedulewithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)

第一个参数还是接口任务,第二个参数是初始延迟时间,第三个参数是任务执行的延迟时间,最后一个参数还是时间的枚举类型;

java 复制代码
public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleWithFixedDelay(()->{
            System.out.println("任务执行了");
        },1,2, TimeUnit.SECONDS);

        Thread.sleep(10000);
        service.shutdown();
    }
}

这两个有啥区别呢,感觉是很像的,第一个scheduleAtFixedRate是延迟一定时间后按固定的时间来执行,这个是严格执行的,不会根据任务执行的时间来往后推,如果这个任务执行了1s那么距离规定的时间就还剩1s了,如果使用schedulewithFixedDelay的就是延迟执行,如果我们这个任务执行了1s那么距离执行下一个任务的时间还是2s,另外scheduledExecutorService因为是基于线程池实现,所以在发生异常时是不会终止的,而单线程的Timer是会随着异常而终止的;

讲了这么多了,我们来模拟实现一下定时器吧,

首先为我们来思考一下怎么实现呢,我们要创建一个MyTimer类,其中包含我们要使用的优先级队列,我们来实现Schedule方法,实现将任务和要执行的时间,还要实现Mytimer中有线程来执行任务,之后还有实现任务类,我们来试试;

1,先来任务类:

java 复制代码
public class TimerTask implements Comparable<TimerTask>{
    private long time;
    private Runnable task;
    public TimerTask(long time,Runnable runnable){
        this.time = time;
        this.task = runnable;
    }
    @Override
    public int compareTo(TimerTask o) {
        return (int)(this.time-o.time);
    }

    public void run(){
        task.run();
    }

    public long getTime(){
        return time;
    }
}

任务类中有记录的时间long类型的time,一个Runnable任务,在任务初始化的时候把时间和任务都初始化了,实现run方法和getTime方法,在实现comparable接口为了优先级队列做准备,我们想要把小的时间先执行,再来MyTimer

java 复制代码
public class MyTimer {
    private PriorityQueue<TimerTask> priorityQueue = new PriorityQueue<>();
    Object locker = new Object();
    public void schedule(Runnable runnable,long time){
        TimerTask timerTask = new TimerTask(System.currentTimeMillis()+time,runnable);
        synchronized (locker){
            priorityQueue.offer(timerTask);
            locker.notify();
        }
    }

    public MyTimer(){
        Thread t = new Thread(()->{
            while(true){
                synchronized (locker){
                    while(priorityQueue.isEmpty()){
                        try {
                            locker.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    TimerTask task = priorityQueue.peek();
                    if (task.getTime()>System.currentTimeMillis()){
                        try {
                            locker.wait(task.getTime()-System.currentTimeMillis());
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{
                        task.run();
                        priorityQueue.poll();
                    }
                }
            }
        });

        t.start();
    }
}

初始化一个优先级队列,大堆还是小堆要在TimerTask中的compareTo中修改,实现schedule方法,创建一个任务对象,参数的任务用传入的,时间用当前入队列的时间戳加上传入的时间,之后将任务放到队列中去,因为优先级队列是不安全的,我们要在入队列的时候加锁,接下来就是线程,我们在MyTimer构造方法中,创建一个线程,之后让线程循环的去队列中取得任务,这里因为还是涉及优先级队列,所以我们还是要加锁,我们要循环判断队列是否为空,为啥要使用循环,因为要避免入队列操作的notify唤醒当前的wait之后,另一个线程抢先执行了任务,但是当前的线程就会去peek空的队列了,所以我们循环判断,就是为了要避免误唤醒,我们在拿到了任务之后,如果当前时间还没到我们要执行的时间我们就用wait(定时)来等待指定时间来继续执行,我们这里不用sleep,之前说过,我们基本是不会使用sleep的因为sleep会抱着锁睡,虽然不占用Cpu资源了,但是就这么停着,不符合我们预期的;

java 复制代码
public class test {
    public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(()->{
            System.out.println("已经过了2秒 执行任务");
        },2000);

        myTimer.schedule(()->{
            System.out.println("已经过了5秒 执行任务");
        },5000);


    }
}

我们来测试一下;

自己观察一下,会根据时间来运行的,本期文章就到这里啦;

大家继续加油!

相关推荐
C_V_Better7 分钟前
Spring Security 如何防止 CSRF 攻击?
java·开发语言·数据结构·后端·算法·spring·csrf
练习&两年半22 分钟前
C语言:51单片机 基础知识
开发语言·51单片机
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS酒店管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源
充满诗意的联盟1 小时前
DDD该怎么去落地实现(4)多对多关系
java·开发语言
wyz09231 小时前
python 之协程笔记
开发语言·笔记·python
快乐非自愿1 小时前
Java中使用FFmpeg拉取RTSP流
java·开发语言·ffmpeg
天天向上杰2 小时前
地基简识Spring MVC 组件
java·spring·mvc·springmvc
武昌库里写JAVA2 小时前
【Redis学习】Redis Docker安装,自定义config文件(包括RDB\AOF setup)以及与Spring Boot项目集成
java·开发语言·spring boot·学习·课程设计
是一只派大鑫2 小时前
从头开始学SpringMVC—02获取请求参数&向域对象共享数据
java·后端·springmvc
原来是猿2 小时前
蓝桥备赛(六)- C/C++输入输出
c语言·开发语言·c++