[Java Web]定时器(Timer) 线程池

使用 Timer

// 使用定时器
public class UseTimer {
    public static void main(String[] args) {
        // 一个定时器 + n 个定时器要去执行的任务
        Timer timer = new Timer();   // 闹钟

        // 继承 TimerTask(抽象类)
        // 执行的是 task.run() 方法
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行任务");
            }
        };
        TimerTask t2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("另一个任务");
            }
        };

        // 指定什么去执行
        // delay: 延时,单位是 ms
        // 一次性的
        //timer.schedule(task, 5000);

        // fixed: 固定的
        // rate: 频率
        // period: 周期
        timer.scheduleAtFixedRate(task, 5000, 1000);

        timer.schedule(t2, 3000);
        
        while(true); // 这个死循环的目的是延时,随后的任务不是在主线程中执行
    }
}

实现 Timer

如何实现Timer:

1.每个任务创建一个线程 + 休眠方式         缺点:每一个任务都需要关联一个线程

import java.util.concurrent.TimeUnit;

public class ImplTimer {
    static class MyThread extends Thread{
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            while (true){
                System.out.println("执行任务");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();

        while (true);
    }
}

2.官方做法:一个线程,完成所有任务的执行

生产者(schedule 的线程) - 消费者(Worker)

放入队列 t1(delay = 5)、t2(delay = 3)、t3(delay = 1)

取任务,可能要等待一段时间、然后执行任务,t1、t2、t3 应该先取出 delay 最小的,这是一个优先级阻塞队列

但这个 delay 不断的在变化,因为前面 t3 等了 1s 之后,那么  t2 再等 2s 就可以执行了,这样等待的时间一直是变化的,因此我们 delay = runAt(任务运行时间(不会变)) - now(当前时间(一直在变))。我们取出 runAt 最小的。

流程:

1.准备好义格优先级队列(要在P/C两个线程中共享)

2.准备好一个等待/通知 对象(要在P/C两个线程中共享)

3.创建好一个 Worker 线程(传入 队列 + 通知对象),并启动

P线程:

  1. 根据 delay + now 计算 runAt
  2. 将 任务(TimerTask)放入队列
  3. 利用通知对象通知(通知的是 C 中的 3)

C线程:

  1. 获取最近的任务(TimerTask)
  2. 得到最近任务的延迟 delay' = runAt - now'
  3. 带条件的休眠
  4. 醒来之后,如果任务时间已到,执行任务;否则,先把任务放回去
  5. 循环回 1 继续

sleep VS wait(timeout)

sleepwait(timeout)
休眠带超时的等待
结束条件唯一

结束条件两个:

1.timeout已到      2.被唤醒

和锁没关系(不会释放)

释放锁(只会释放当前对象的锁)

Thread staticObject

线程池(Thread Pool)

主线程读取输入                                                有客人点菜

创建线程计算                                                    雇一个厨师 做饭

        创建线程 + 计算 + 销毁线程                      解雇厨师

生产者——消费者模型(多消费者)

1.Executor接口        一种执行任务的东西/人

        void execute(Runnable command);

2.ExecutorService接口

3.ThreadPoolExecutor实现类

        重点:构造方法的参数理解、创建线程的流程

线程池:公司       

正式工:一种工作线程、永远不会被“解雇”

临时工:一种工作线程、临时工空闲太久会被解雇

int corePoolSize        正式员工的数量上限(编制)

int maximumPoolSize        所有员工的数量上限(编制) 正式 + 临时

long keepAliveTime + TimeUnit unit :允许临时工摸鱼多久

BlockingQueue workQueue:传递任务的阻塞队列

ThreadFactory factory:让我们可以介入线程的创建流程

Reject(拒绝) ExecutionHandler:拒绝策略

 4.线程池的流程如下(按需创建)

  1. 一开始公司一个员工都没有
  2. 当有任务来了之后,当前正式工的数量 < 正式工的上限 一律招一个新的正式员工处理业务(即使有其他人还在空闲中)、把任务放入阻塞队列中、如果阻塞队列满了,雇佣临时工执行任务(临时工的数量 < 临时工的上限)
  3. 处理不过来执行拒绝策略

5.怎么让摸鱼的正式员工也被解雇?

shutdown()    VS    shutdownNow()

6.自己实现线程池

import java.util.concurrent.*;

// 线程池类
public class MyThreadPoolExecutor implements Executor {
    // 创建线程的工厂对象
    private final ThreadFactory threadFactory;

    // 临时工摸鱼的时间上限
    private final long keepAliveTime;
    private final TimeUnit unit;

    // 当前正式员工的数量
    private int currentCoreSize;

    // 正式员工的数量上限
    private final int corePoolSize;

    // 当前临时员工的数量
    private int currentTemporarySize;

    // 临时员工的数量上限
    private final int temporaryPoolSize;

    // 传递任务的阻塞队列
    private final BlockingQueue<Runnable> workQueue;

    public MyThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory,
                                RejectedExecutionHandler handler) {
        this.corePoolSize = corePoolSize;
        this.temporaryPoolSize = maximumPoolSize - corePoolSize;
        this.workQueue = workQueue;
        this.threadFactory = threadFactory;
        this.keepAliveTime = keepAliveTime;
        this.unit = unit;
    }

    // 向线程池中提交任务
    // 目前这个方法不是线程安全版本的
    @Override
    public void execute(Runnable command) {
        // 1. 如果正式员工的数量还低于正式员工的数量上限,则优先创建正式员工处理任务
        // 1.1 需要管理,当前正式员工有多少,正式员工的数量上限有多少?
        if (currentCoreSize < corePoolSize) {
            // 优先创建正式员工进行处理
            // 创建一个线程,这个线程中的任务就是不断地取任务-做任务,但是不需要考虑退出的问题
            CoreJob job = new CoreJob(workQueue, command);
//            Thread thread = new Thread(job);    // 不使用工厂创建的线程
            Thread thread = threadFactory.newThread(job);   // thread 代表的就是正式员工
            String name = String.format("正式员工-%d", currentCoreSize);
            thread.setName(name);

            thread.start();

            // 只是两种不同的策略,没有谁是正确的说法
            // 1. 把 command 放到队列中;command 的执行次序是在队列已有的任务之后
            // 2. 创建正式员工的时候,就把 command 提交给正式员工,让 command 优先执行
            // 我们这里采用第二种方案,主要原因就是 java 官方的就是使用的第二种策略

            currentCoreSize++;
            return;
        }

        // 走到这里,说明正式员工的数量 == 正式员工的上限了
        // 2. 优先把任务放入队列中,如果放入成功,execute 执行结束,否则还需要继续
        // 2.1 需要一个阻塞队列
//        workQueue.put(command); // 带阻塞的放入,是否满足这里的需求?
                                // 我们这里希望的是立即得到结果
        boolean success = workQueue.offer(command);
        if (success == true) {
            // 说明放入队列成功
            return;
        }

        // 队列也已经放满了
        // 3. 继续判断,临时工的数量有没有到上限,如果没有到达,创建新的临时工来处理
        if (currentTemporarySize < temporaryPoolSize) {
            // 创建临时工进行处理
            TemporaryJob job = new TemporaryJob(keepAliveTime, unit, workQueue, command);
//            Thread thread = new Thread(job);    // 不使用工厂创建的线程
            Thread thread = threadFactory.newThread(job);   // thread 代表的就是临时员工
            String name = String.format("临时员工-%d", currentTemporarySize);
            thread.setName(name);

            thread.start();

            currentTemporarySize++;
            return;
        }

        // 4. 执行拒绝策略
        // 为了实现方便,暂时不考虑其他策略
        throw new RejectedExecutionException();
    }
}
import java.util.concurrent.BlockingQueue;

// 一个正式员工线程要完成的工作
public class CoreJob implements Runnable {
    // 需要阻塞队列
    private final BlockingQueue<Runnable> workQueue;
    private Runnable firstCommand;

    CoreJob(BlockingQueue<Runnable> workQueue, Runnable firstCommand) {
        this.workQueue = workQueue;
        this.firstCommand = firstCommand;
    }

    @Override
    public void run() {
        try {
            firstCommand.run();     // 优先先把刚提交的任务先做掉了
            firstCommand = null;    // 这里设置 null 的意思是,不影响 firstCommand 对象被 GC 时的回收

            while (!Thread.interrupted()) {
                Runnable command = workQueue.take();
                command.run();
            }
        } catch (InterruptedException ignored) {}
    }
}
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

// 一个临时员工线程要完成的工作
public class TemporaryJob implements Runnable {
    // 需要阻塞队列
    private final BlockingQueue<Runnable> workQueue;
    private final long keepAliveTime;
    private final TimeUnit unit;
    private Runnable firstCommand;

    TemporaryJob(long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, Runnable firstCommand) {
        this.keepAliveTime = keepAliveTime;
        this.unit = unit;
        this.workQueue = workQueue;
        this.firstCommand = firstCommand;
    }

    @Override
    public void run() {
        try {
            firstCommand.run();     // 优先先把刚提交的任务先做掉了
            firstCommand = null;    // 这里设置 null 的意思是,不影响 firstCommand 对象被 GC 时的回收

            // 一旦超过一定时间没有任务,临时工是需要退出的
            // 1. keepAliveTime + unit 记录起来
            // 2. 怎么就知道超过多久没有任务了?如果一定时间内都无法从队列中取出来任务,则认为摸鱼时间够了
            while (!Thread.interrupted()) {
//                Runnable command = workQueue.take();
                Runnable command = workQueue.poll(keepAliveTime, unit);
                if (command == null) {
                    // 说明,没有取到任务
                    // 说明超时时间已到
                    // 说明该线程已经 keepAliveTime + unit 时间没有工作了
                    // 所以,可以退出了
                    break;
                }
                command.run();
            }
        } catch (InterruptedException ignored) {}
    }
}
import java.util.concurrent.*;

public class Main {
    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(15);
            } catch (InterruptedException ignored) {}
        }
    }

    static class MyThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r);
        }
    }

    public static void main(String[] args) {
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(5);
        // 同时最多有 15 个任务
        // 3 个正式的
        // 5 个队列中
        // 7 个临时的
        // 提交第 16 个任务时就会出现拒绝服务
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(
                3, 10, 10, TimeUnit.SECONDS,
                workQueue,
                new MyThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // [0, 1, 2] 交给正式员工在处理
        // [3, 4, 5, 6, 7] 暂时放在队列中
        // [8, 9, 10, 11, 12, 13, 14] 交给临时工处理
        // 过了 15s 之后,第一批任务执行结束
        // [0, 1, 2]、[8, 9, 10, 11, 12, 13, 14] 执行结束
        // 剩下的 [3, 4, 5, 6, 7] 任务具体怎么分配不确定,大概率是交给正式员工执行
        // 就算极端情况下,5 个全部给了临时工
        // 也至少还有 2 个临时工没有工作
        // 再过 10s,至少 2 个,最多 5 个临时工要被解雇
        for (int i = 0; i < 15; i++) {
            System.out.println("提交任务: " + i);
            Task task = new Task();
            executor.execute(task);
        }
    }
}

线程池总结:(⭐)

  1. 为什么
  2. 方法 + 机制
    • 构造方法的含义
    • execute的使用
    • 其他的使用暂时没有介绍
  3. 自己实现(难点)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值