定时器 + 线程池

目录

一 . 定时器

 1) 实现一个定时器

二 . 线程池 

 三 . 线程池的实现


一 . 定时器

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

Java标准库中提供的定时器 , 在 java.util 包下的 Timer 类

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

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

schedule : 安排

 1) 实现一个定时器

定时器的内部管理着很多任务 , 每个任务的触发时间是不同的 , 通过一个 / 一组 工作线程, 每次都找到这些任务中最先到到达时间的任务~~

一个线程 , 先执行最早的任务 , 然后再去执行第二早的 , .......

如果任务的时间到了 , 就执行 , 没到就等

定时器 , 可能有多个线程在执行schedule方法 , 因此在多线程的情况下要保证线程安全 , 

使用优先级队列来管理任务.

首先 建造一个MyTask 要包含要执行的任务 , 时间 .

package Demo5;

import java.util.concurrent.PriorityBlockingQueue;

// 表示一个任务
class MyTask implements Comparable<MyTask>{
    // 使用Runnable来表述一个任务
    public Runnable runnable;
    // 为了方便后序判定 , 这里使用绝对的时间戳
    public long time;

    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        // 绝对时间戳 = 当前的时间戳 + delay , 作为该任务的实际执行的时间戳
        this.time = System.currentTimeMillis() + delay;
    }

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

public class MyTimer {
    // 使用优先级阻塞队列 即实现了线程安全 并且 还可以使线程处于阻塞状态
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    // 提供schedule 方法 用来安排任务 和 时间
    public void schedule(Runnable runnable, long delay) {
        MyTask myTask = new MyTask(runnable,delay);
        // 放入队列中
        queue.put(myTask);
    }
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    MyTask task = queue.take();
                    if (task.time <= System.currentTimeMillis()) {
                        // 时间到了 可以执行任务了
                        task.runnable.run();
                    } else {
                        // 如果时间还没到  再把取出的任务放回队列
                        queue.put(task);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}
package Demo5;

public class Test {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1");
            }
        },2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2");
            }
        },1000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3");
            }
        },100);
    }
}

 解决方案 : 

可以基于wait 这样的机制来实现~~

wait 有一个版本 , 指定等待时间 , (不需要notify 唤醒 , 时间到了自然就醒了)

只需要计算出当前时间和任务目标之间的时间差 , 就等待这么长时间就可以了.

对此 , 我们就发现wait 和 sleep 都可以指定一段时间 , 为啥不用sleep呢 ?

原因在于sleep 不能中途唤醒 , 而 wait 可以被notify 唤醒.

例如 : 在我们等待的过程中 , 又有一个新的任务放入了队列中 , 新的任务可能被执行的时间是最前面的 , 但是此时我们等待的还是之前的任务, 因此每当我们加入一个新的任务时 , 就要通过notify来唤醒wait.

在代码中改变这两句 , 就可以解决忙等问题!

synchronized (locker) {
            locker.notify();
        }
locker.wait( task.time - System.currentTimeMillis());

重点注意加锁的位置 , 不要让notify "空打一炮".

整体代码 : 

package Demo5;

import java.util.concurrent.PriorityBlockingQueue;

// 表示一个任务
class MyTask implements Comparable<MyTask>{
    // 使用Runnable来表述一个任务
    public Runnable runnable;
    // 为了方便后序判定 , 这里使用绝对的时间戳
    public long time;

    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        // 绝对时间戳 = 当前的时间戳 + delay , 作为该任务的实际执行的时间戳
        this.time = System.currentTimeMillis() + delay;
    }

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

public class MyTimer {
    // 使用优先级阻塞队列 即实现了线程安全 并且 还可以使线程处于阻塞状态
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    // 提供schedule 方法 用来安排任务 和 时间
    public void schedule(Runnable runnable, long delay) {
        MyTask myTask = new MyTask(runnable,delay);
        // 放入队列中
            queue.put(myTask);
        synchronized (this) {
            this.notify();
        }
    }
    public MyTimer() {
        Thread t = new Thread(() -> {
            synchronized (this) {
                while (true) {
                    try {
                        MyTask task = queue.take();
                        if (task.time <= System.currentTimeMillis()) {
                            // 时间到了 可以执行任务了
                            task.runnable.run();
                        } else {
                            // 如果时间还没到  再把取出的任务放回队列
                            queue.put(task);
                            this.wait( task.time - System.currentTimeMillis());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
    }
}

二 . 线程池 

池的目的就是提高效率 .

线程池 : 提前把线程准备好 , 创建线程不再是直接从系统申请, 而是从池子里拿.

线程用完后 , 再放回池子. 

进程 比较重 , 频繁创建销毁 , 开销大 , 解决方案就是 : 进程池 或者 线程

线程 虽然比进程轻了 , 但是如果频繁的创建和销毁 , 开销仍然还是存在的 , 解决方法 : 线程池 或者 协程.

为什么线程放在池子里 , 就比从系统这边申请来的更快呢?

我们自己写的代码 , 称为"用户态"运行的代码

而其中有一些代码 需要调用操作系统的API , 进一步的逻辑会在内核中执行 .

在内核中运行的代码 , 称为"内核态"运行的代码.

创建线程 , 本身就需要内核的支持 (创建线程本质上就是在内核中搞个PCB , 加到链表中)

而把创建好的线程放到池子里 , 这个池子是用户态自己实现的 , 线程从池子中取 / 往池子中放 都是用户态自己实现的 , 在这个过程中不需要涉及到内核态 , 就是纯粹的用户态代码就可以完成 .  

举个栗子 : 

好比让你的朋友给你从外面带个东西 , 和你自己去商店买.

让别人带这个时间是不可控的 , 带回来不知道是什么时候了.(可能10分钟 或者1个小时, 两个小时 ,)

而如果自己去买这个时间是可控的.

总结 : 纯用户态操作 , 时间是可控的 .

          涉及到内核态操作, 时间就太不可控了.

Java 标准库中的线程池 : ThreadPoolExecutor

标准库中提供了一个简化版本的线程池 :

Executors : 本质上是针对ThreadPoolExecutor 进行了封装 , 提供了一些默认参数.

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

public class Demo6 {
    public static void main(String[] args) {
        // 创建一个固定线程数目的线程池 , 参数指定了线程的个数
        ExecutorService pool = Executors.newFixedThreadPool(10);
        // 创建一个自动扩容的线程池 , 会根据任务量来自动进行扩容
        //Executors.newCachedThreadPool();
        // 创建一个只有一个线程的线程池
        //Executors.newSingleThreadExecutor();
        // 创建一个带有定时器功能的线程池 , 类似于Timer
        //Executors.newScheduledThreadPool(10);
        for (int i = 0; i < 100; i++) {
            int num = i;
            pool.submit(() -> {
                System.out.println(num);
            });
        }
    }
}

附加 : 虽然线程池的参数这么多 , 但是使用的时候最最重要的还是核心参数的设定最重要!

 正确的做法 : 要通过性能测试的方法 , 找到一个最合适的值.

例如 : 写一个服务器程序 , 服务器里通过线程池 , 多线程池来处理用户请求.

根据实际的场景 , 根据测试用户的请求量 (500/1000/2000) , 构造一个合适的值 . 

当线程数多了 , 整体的速度是会变快 , 但是CPU占用率会高(占用率太高 如果突然来一波请求的峰值 此时服务器可能就挂了)

当多线程少了 , 整体的速度是会变慢 , 但是CPU占用率会下降.

需要做的是从上述两个中取到一个中间值. 通过性能测试找到那个平衡点.

标准库提供的四种拒绝策略 : 

 三 . 线程池的实现

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

class MyThreadPool {
    // 1. 描述一个任务 , 直接使用Runnable
    // 2. 使用阻塞队列来组织任务
    BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();
    // 3. 描述一个线程 , 工作线程的功能就是从任务队列中取任务并执行的.
    static class Worker extends Thread {
        BlockingQueue<Runnable> queue = null;
        public Worker(BlockingQueue<Runnable> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Runnable runnable = queue.take();
                    runnable.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 4. 创建List来组织若干个线程
    private List<Worker> workers = new ArrayList<>();

    // 构造方法
    public MyThreadPool(int n) {
        for (int i = 0; i < n; i++) {
            Worker worker = new Worker(queue);
            worker.start();
            workers.add(worker);
        }
    }
    // 5. 创建一个方法 , 安排任务
    public void submit(Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Demo7 {
    public static void main(String[] args) {
        MyThreadPool myThreadPool = new MyThreadPool(10);
        for (int i = 0; i < 100; i++) {
            int num = i;
            myThreadPool.submit(() -> {
            System.out.println(num +"执行线程: "+ Thread.currentThread().getName());
            });
        }
    }
}

将任务分给了10个线程 , 每个线程都去执行一部分任务.

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值