【JavaEE】多线程代码实例:定时器与线程池

目录

定时器:

什么是定时器? 

标准库中的定时器: 

实现定时器: 

​线程池: 

什么是线程池? 

标准库中的线程池: 

工厂模式: 

线程池的实现: 


定时器:

什么是定时器? 

定时器也是软件开发中的一个重要组件。类似于一个 "闹钟".。达到一个设定的时间之后, 就执行某个指定好的代码。这与我们平时说的闹钟很类似,但是它不仅可以起到提醒的作用,还可以真正的去做。并且定时器在网络编程中的用途还是比较多的。比如:在我们访问一个网页的时候,设置一个定时器,一旦访问的时间过久,就会触发定时器,并结束本次访问,及时断开连接,不再阻塞等待。

标准库中的定时器: 

标准库中提供了一个Timer类,我们可使用这个Timer来做我们想定时做的事情。

Timer类的核心方法是schedule,它包含两个参数。第一个参数指定即将执行的任务代码,第二个参数指定多长时间后执行任务(单位ms)

import java.util.Timer;
import java.util.TimerTask;

public class ThreadDemo1 {
    public static void main(String[] args) {
        //设置定时器
        Timer timer=new Timer();
        System.out.println("已经设置好定时器!!!");
        //指定定时器的任务和执行时间
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行任务1!!");
            }
        },1000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行任务2!!");
            }
        },2000);

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

实现定时器: 

//实现一个定时器

import java.util.concurrent.PriorityBlockingQueue;

/**
 *  实现定时器的核心:
 *  1、定时器需要让任务有一个放置的地方(考虑使用优先级阻塞队列,可以实现线程安全和优先级的情况)
 *  2、定时器需要让任务在指定的时间进行执行(考虑使用一个线程不断进行扫描来进行判断)
 */
//任务
class MyRunnable implements Comparable<MyRunnable>{
    //任务使用Runnable创建
    private Runnable runnable;
    //推迟时间(单位:ms)
    private long time;
    //构造方法
    public MyRunnable(Runnable runnable,long time){
        this.runnable=runnable;
        this.time=time;
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }
    //执行任务
    public void run(){
        runnable.run();
    }
    //重写

    @Override
    public int compareTo(MyRunnable o) {
        return (int)(this.time-o.time);
    }
}

class MyTimer{
    //使用object来控制阻塞队列执行顺序
    private Object loker=new Object();
    //需要一个优先级阻塞队列
    PriorityBlockingQueue<MyRunnable>queue=new PriorityBlockingQueue<>();
    //一个线程用来扫描队列中的任务(需要在构造方法中实现)
    public MyTimer(){
        Thread t=new Thread(()->{
            while(true){
                try {
                    synchronized (loker){
                        MyRunnable task=queue.take();
                        //记录当前的时间戳
                        long curTime=System.currentTimeMillis();
                        //判断是否到达执行时间
                        //如果没有到达执行时间就把任务再放回阻塞队列当中
                        if(curTime< task.getTime()){
                            queue.put(task);
                            //进行阻塞
                            loker.wait(task.getTime()-curTime);
                            //如果到达执行时间则直接执行任务
                        }else{
                            task.run();
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        t.start();

    }
    public void schedule(Runnable runnable,long after){
        MyRunnable runnable1=new MyRunnable(runnable,System.currentTimeMillis()+after);
        queue.put(runnable1);
        synchronized (loker){
            loker.notify();
        }

    }

}


public class MyTask {
    public static void main(String[] args) {
        MyTimer timer1=new MyTimer();
        timer1.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("到达任务1执行时间,执行任务1!");
            }
        },1000);
        MyTimer timer2=new MyTimer();
        timer2.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("到达任务2执行时间,执行任务2!");
            }
        },3000);

    }

}

线程池: 

什么是线程池? 

提到线程池,我们应该可以想到了解过的很多类似的“池”,比如字符串常量池。虽然我们说创建线程和销毁线程的开销比进程小,但是不可否认的是每次创建和销毁也是会消耗很多资源的。(多少只是相对来说),为了让开销更小,我们引入了线程池。

线程池的最大好处就是可以每次启动和销毁线程的开销!

试想一下,现在有10个线程,我们是一次创建好放到线程池中备用消耗的资源少还是一个一个创建开销小?答案当然是一次创建10个,一个一个创建,每次都要消耗资源,每次销毁也需要消耗资源,而一次创建多个这就大大减少了开销。 

标准库中的线程池: 

标准库中的线程池使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池。
返回值类型为 ExecutorService,通过 ExecutorService.submit 可以注册一个任务到线程池中。 

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPool1 {
    public static void main(String[] args) {
        //在线程池中创建10个线程
        ExecutorService pool= Executors.newFixedThreadPool(10);
        for (int i = 1; i <=10; i++) {
            int n=i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程池中执行任务"+n);
                }
            });

        }
    }
}

 

我们这里用的Executors这个类的静态方法,直接构造出对象来,并没有使用new的操作,使用类的静态方法构造对象相当于是把new操作隐藏到静态方法里边了。然后使用submit方法,将任务以Runnable接口的方式给线程池即可。 

Executors 本质上是 ThreadPoolExecutor 类的封装。 

工厂模式: 

这里就要提到一种设计模式——工厂模式 

把new操作隐藏在静态方法里边的方法就是工厂方法。提供工厂方法的类就叫做工厂类。这种设计模式叫做工厂模式。 工厂模式有什么好处?我们知道设计模式很大程度上是为了填补语法上的一些坑,而工厂模式主要是填补了构造方法的坑,并且在创建对象的时候不会对客户端暴露创建逻辑。比如在创建坐标系的时候有笛卡尔和极坐标两种,一般情况下我们参数都是double,这个时候想要通过重载完成创建就不能成功了。就需要我们借助构造方法了。

上面是其中一个简单的使用,关于线程池的创建还有很多方式,如:

 

同时有个需要注意的点就是我们main线程结束的时候,程序并没有停止,因为我们线程池中的线程属于前台线程,会阻止我们的进程结束。同时需要注意的是我们上面的定时器中的任务也是前台线程。这个时候想要停止就需要我们点击上面的停止按钮了。

线程池的实现: 

1、核心操作为 submit, 将任务加入线程池中。
2、 使用 Runnable 描述一个任务。
3、使用一个 BlockingQueue 组织所有的任务。
4、每个线程要做的事情: 不停的从 BlockingQueue 中取任务并执行。
5、指定一下线程池中的最大线程数 n; 当当前线程数超过这个最大值时, 就不再新增线程了。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool{
    //阻塞队列当中放入任务
    private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();
    //构造方法中创建出工作的线程
    public MyThreadPool(int n){
        for (int i = 0; i < n; i++) {
            //创建线程
            Thread t=new Thread(()->{
                while(true){
                    //任务为空
                    Runnable runnable= null;
                    try {
                        //将阻塞队列中的任务拿出给runnable
                        runnable = queue.take();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    //执行任务
                    runnable.run();
                }
            });
            t.start();
        }
    }

    //把任务放到阻塞队列中(注册任务)
    public void submit(Runnable runnable){
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

public class ThreadPool2 {
    public static void main(String[] args) {
        //创建一个带有10个线程的线程池
        MyThreadPool myThreadPool=new MyThreadPool(10);
        for (int i = 0; i <10 ; i++) {
            int n=i+1;
            myThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行线程池中任务:"+n);
                }
            });
        }
    }
}

猜你喜欢

转载自blog.csdn.net/m0_67995737/article/details/129059156