线程实操&&定时器&&线程池

前言:

        本文将介绍线程中使用的定时器与线程池,定时器就像一个闹钟,在定好的时间到达后便开始执行代码。线程池类似于我们所熟悉的字符串常量池、数据库连接池等等,使用线程池能更加高效的创建线程和销毁线程。

一、定时器

1.定时器是什么

        定时器也是软件开发中的一个重要组件. 类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定好的代码.

2.标准库内的定时器

        标准库中提供了一个 Timer 类 . Timer 类的核心方法为 schedule .
        schedule 包含两个参数 . 第一个参数指定即将要执行的任务代码 , 第二个参数指定多长时间之后执行 ( 单位为毫秒 )
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("hello");
   }
}, 3000);

3.实现定时器

定时器的构成:

  • 一个带优先级的阻塞队列
    • 为啥要带优先级呢?
    • 因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.
  • 队列中的每个元素是一个 Task 对象.
  • Task 中带有一个时间属性, 队首元素就是即将
  • 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

实现代码(内附部分代码说明): 

/**
 *  定时器的构成:
 * 一个带优先级的阻塞队列
 * 队列中的每个元素是一个 Task 对象.
 * Task 中带有一个时间属性, 队首元素就是即将
 * 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
 */
public class MyTimerDemo {

    //Task 类用于描述一个任务(作为 Timer 的内部类).
    //里面包含一个 Runnable 对象和一个 time(毫秒时间戳)这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口.
    static class Task implements Comparable<Task>{
        private Runnable command;
        private long time;
        public Task(Runnable command , long time){
            this.command = command;
            this.time = System.currentTimeMillis() + time;
        }
        public void run(){
            command.run();
        }
        @Override
        public int compareTo(Task o){
            return (int)(this.time - o.time);
        }
    }
    //通过 PriorityBlockingQueue 来组织若干个 Task 对象.
    //通过 schedule 来往队列中插入一个个 Task 对象.
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
    private Object checker = new Object();
    //Worker类用于一直检查队列中最快到达启动时间的线程,但是需要设置锁来解决忙等问题,
    class Worker extends Thread{
        @Override
        public void run(){
            while(true){
                try {
                    Task task = queue.take();
                    long currentTime = System.currentTimeMillis();
                    if(task.time > currentTime){
                        queue.put(task);
                        //设置锁解决忙等问题不然线程会一直在检查还没开始的线程
                        //就像你计划3点30学习,但是还没到3.30,你就一直在看时间,什么都没干,所以造成浪费宝贵的30分钟,你全用来检查有没有到钟了
                        synchronized(checker) {
                            checker.wait(task.time - currentTime);
                        }
                    }else{
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
    public MyTimerDemo(){
        //创建定时器类时,将检查类实例化
        Worker worker = new Worker();
        //检查线程开始
        worker.start();
    }

    //核心代码:schedule方法安排任务,并指定任务多久后执行
    public void schedule(Runnable command , long after){
        //参数after表示当前时间后过多长时间开始执行任务
        Task task = new Task(command , after);
        queue.offer(task);
        //将新的线程加载进去
        synchronized (checker){
            // 唤醒正在等待的锁,然后重新检查最先开始的任务
            checker.notify();
        }
    }

    public static void main(String[] args) {
        MyTimerDemo myTimerDemo = new MyTimerDemo();
        Runnable command = new RunnableDemo(){
            @Override
            public void run(){
                System.out.println("我来了");
                myTimerDemo.schedule(this,3000);
            }
        };
        myTimerDemo.schedule(command , 3000);
    }
}

二、线程池

1.引入线程池

       引入线程池就是想在进一步“强化”线程的有点,相比于进程的创建和销毁,线程的创建和销毁已经够轻量了,但是在更频繁的情况下,创建/销毁线程,系统也有点消耗不起。

        所以想到了线程池这个办法,就是先创建好一定数量的线程放到线程池中,需要使用线程,就从线程池中取出已经创建好的线程来执行任务,结束任务就直接将线程返回池子中。        
        这个过程只要在创建线程池的时候需要和系统打交道,其他【创建】/ 【销毁】线程的时候,只需跑到线程池中【取出来】/【放回去】就好,只要就节省了创建销毁的消耗

2.为什么使用线程池效率更高

这牵扯到一个CPU用户态和内核态的问题。

  • 【用户态】运行用户程序
  • 【内核态】会调用到操作系统内核的程序,操纵硬件 

        

通常认为【用户态】比【内核态】的效率更高,举一个简单的例子:

        👆面这个例子中,将打印任务交给自己就是以【用户态】的状态去解决问题,可以立马得到解决,而将任务交给前台,就是以【内核态】的状态去完成任务,但是这样你不知【内核态】什么时候才能解决问题,可能快可能慢。所以通常情况下,我们都任务【用户态】效率会比【内核态】的高。

        基于此,我们使用【线程池】的原因也就有迹可循了,每次的从线程池中获取/销毁线程都是一个【用户态】可以马上解决燃眉之急,而直接让系统创建,说不定系统在忙别的事情,比较CPU核数就这么多,而且任务还不少,指不定什么时候才给你创建。

         线程池最大的好处就是减少每次启动、销毁线程的损耗

3.标准库内的线程池

  • 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
  • 返回值类型为 ExecutorService
  • 通过 ExecutorService.submit 可以注册一个任务到线程池中.
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
   }
});
Executors 创建线程池的几种方式
  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
Executors 本质上是 ThreadPoolExecutor 类的封装 .

ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定.

4.实现线程池

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

实现代码(内附代码说明): 

public class MyThreadPool {
    private LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    private int maxThreadCounter;
    //提交任务方法
    public void submit(Runnable task){
        try {
            //将任务放到队列内部
            queue.put(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public MyThreadPool(int maxThreadCounter){
        this.maxThreadCounter = maxThreadCounter;
        for(int i = 0 ; i < this.maxThreadCounter ; i++){
            //创建线程,将阻塞队列的引用作为参数
            Worker worker = new Worker(queue);
            worker.start();
        }
    }
    public static void main(String[] args) {
        //实现自定义线程池
        MyThreadPool myThreadPool = new MyThreadPool(10);
        for(int i = 0 ; i < 100 ; i++){
            //往线程池中提交任务
            myThreadPool.submit(() -> System.out.println("上号!!!"));
        }
    }
}
//执行线程
class Worker extends Thread{
    //设置变量引用阻塞队列
    private LinkedBlockingQueue<Runnable> queue;
    public Worker(LinkedBlockingQueue<Runnable> queue){
        super("worker");
        this.queue = queue;
    }
    @Override
    public void run(){
        try {
            //只要不执行中断,线程就从队列中取出任务来执行
            while(!Thread.currentThread().isInterrupted()){
                Runnable task = queue.take();
                task.run();
            }
        } catch (InterruptedException e) {

        }
    }
}

个人笔记使用~~ 

感谢阅读

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼儿hzj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值