1.Timer
1.1Timer基本介绍
1.Timer的主要作用
任务调度:Timer允许你安排一个任务在未来的某个时间点执行,或者以固定的间隔重复执行
 后台执行:Timer可以使用一个后台线程来执行任务,这意味着调度和执行任务不会阻塞主线程(主线程结束后后台线程跟着结束)
 简单易用:Timer提供了一个相对简单的方式来处理定时任务,适合用于不需要复杂调度的场景
2.Timer的构造方法
//1.默认构造方法
//创建一个Timer对象,是一个后台线程,并使用线程的默认名字
public Timer() {
        this("Timer-" + serialNumber());
}
//2.指定线程名字的构造方法
//创建一个Timer对象,是一个后台线程,并使用指定的线程名字
public Timer(String name) {
        thread.setName(name);
        thread.start();
}
//3.指定是否为后台线程的构造方法
//传入true,是后台线程;传入false,是前台线程
public Timer(boolean isDaemon) {
        this("Timer-" + serialNumber(), isDaemon);
}
//4.指定线程名字和是否为后台线程的构造方法
public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
}
3.Timer中的schedule方法
(1)schedule(TimerTask task, Date time):安排任务在指定的时间执行一次
public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("延迟三秒执行");
            }
        };
        //使用Date对象来指定具体的执行时间
        //new Date(System.currentTimeMillis()+1000表示当前时间等待1000ms
        timer.schedule(timerTask,new Date(System.currentTimeMillis()+1000));
}
(2)schedule(TimerTask task, Date firstTime, long period):安排任务在指定的时间首次执行,然后每隔一段时间重复执行
public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("延迟三秒执行");
            }
        };
        //当前时间等待1000ms后第一次执行任务
        //此后每间隔1000ms就执行一次任务
        timer.schedule(timerTask,new Date(System.currentTimeMillis()+1000),1000);
}
(3)schedule(TimerTask task, long delay):安排任务在指定的延迟时间后执行一次(相对于当前时间)
public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("延迟三秒执行");
            }
        };
        //当前时间延迟3000ms后执行
        timer.schedule(timerTask,3000);
}
(4)schedule(TimerTask task, long delay, long period):安排任务在指定的延迟时间后首次执行,然后每隔一段时间重复执行
public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("延迟三秒执行");
            }
        };
        //当前时间延迟3000ms后执行
        //此后每间隔3000ms就执行一次任务
        timer.schedule(timerTask,3000,3000);
}
问题:上述构造方法中,使用Date对象指定时间和使用long类型数据指定时间有什么区别?
Date对象可以指定一个具体的时间,比如在2025年1月1日12时01分01秒执行任务;
而传入一个long类型数据表示以当前时间为基准,延迟指定时间后执行任务
1.2模拟实现MyTimer
1.2.1大体框架
MyTimer类:
 (1)Thread线程:用来执行任务
 (2)PriorityQueue(小根堆):存放任务,并且可以按照时间先后顺序取出任务
 (3)MyTimer构造方法:启动thread
 (4)schedule:实例化/添加任务
 任务
 MyTask类
 (1)time:保存任务要执行的时间
 (2)Runnable:重写run方法
 (3)run方法:执行run方法
 (4)getTime:获取人物的执行时间
 (5)compareTo方法:保证放进小根堆的任务必须是可比较的
1.2.2MyTask类

1.2.3MyTime类

1.2.4代码演示

1.3MyTimer源码
import java.util.PriorityQueue;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 38917
 * Date: 2025-03-05
 * Time: 19:26
 */
//描述一个任务
class MyTimerTask implements Comparable<MyTimerTask>{
    private final long time;
    private final Runnable runnable;
    public MyTimerTask(Runnable runnable,long delay){
        this.runnable = runnable;
        this.time = (System.currentTimeMillis() + delay);
    }
    //执行run中的代码块
    public void run(){
        runnable.run();
    }
    //比较
    @Override
    public int compareTo(MyTimerTask o) {
        return (int)(this.time - o.time);
    }
    //获取时间
    public long getTime() {
        return time;
    }
}
class MyTimer {
    //创建线程
    Thread thread;
    //存放任务
    private final PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    //创建锁对象
    private final Object locker = new Object();
    //实例化任务
    public void schedule(Runnable runnable,long delay){
        synchronized (locker) {
            //实例化任务
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
            //添加任务
              queue.offer(myTimerTask);
            locker.notify();
        }
    }
    //扫描任务队列,执行任务的线程
    public MyTimer(){
        thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                //锁是保护队列
                try {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            locker.wait();
                        }
                        MyTimerTask myTimerTask = queue.peek();
                        long curTime = System.currentTimeMillis();
                        if (curTime >= myTimerTask.getTime()) {
                            queue.poll();
                            //执行任务
                            myTimerTask.run();
                        } else {
                            locker.wait(myTimerTask.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(() -> System.out.println("400"),400);
        myTimer.schedule(() -> System.out.println("300"),300);
        myTimer.schedule(() -> System.out.println("200"),200);
        myTimer.schedule(() -> System.out.println("100"),100);
        Thread.sleep(500);
        System.out.println("Hello Main");
    }
}
2.线程池
2.1什么是线程池?
1.概念:线程池是一种管理和复用线程的编程模式。它预先创建一定数量的线程,在执行任务需要时,将任务分配给这些线程,从而提高运行效率
2.主要特点
线程复用:当线程执行完一个任务时,不会立即销毁,而是等待下一个任务的到来(当然这种等待是有时间限制的),这样避免了频繁的创建和销毁线程
动态调整:根据实际环境需要动态调整线程数量,以达到最佳性能
任务队列:线程池会维护一个任务队列,用于存放待执行的任务,当线程空闲时,从队列中取出任务并执行
2.2 标准库线程池参数介绍

 1.int corePoolSize:核心线程数
 2.int maximumPoolSize:最大线程数
 3.long keepAliveTime:非核心线程的空闲时的最大存活时间。假如corePoolSize=4,maximumPoolSize=8,有四个非核心线程,这类线程空闲时,不能一直等待新任务的到来,当等待时间超过keepAliveTime后就会销毁
 4.TimeUnit unit:long keepAliveTime的时间单位
 5.BlockingQueue workQueue:任务队列
 6.ThreadFactory threadFactory:线程工厂,用于创建新线程的工厂
 7.RejectedExecutionHandler handler拒绝策略
2.3线程池的执行流程
假设现在有一个线程池:
 核心线程数2,最大线程数4,等待队列2
 (1)任务数量<=2(A,B)时,由核心线程执行任务
 (2) 2<任务数量<=4(A,B,C,D)时,核心线程无法同时处理所有任务,未被执行的任务(C,D)将会进入等待队列中等待核心线程执行
 (3)4<任务数量<=6(A,B,C,D,E,F),此时等待队列也满了,线程池就会就会开放非核心线程来执行任务,C和D任务继续在等待队列中等待,新添加的E和F任务由非核心线程来执行
 (4)任务数量>6,核心线程,等待队列,非核心线程都被任务所占用,仍然无法满足需求,此时就会触发线程池的拒绝策略
RejectedExecutionHandler handler四大拒绝策略

 1.AbortPolicy:直接抛异常
 
 2.CallerRunsPolicy:由提交该任务的线程来执行
 3.DiscardPolicy:丢弃新任务
 
 4.DiscardOldestPolicy:丢弃最老的任务
 
2.4模拟实现一个简单的线程池

3.线程安全问题总结
(1)使用没有共享资源的模型
(2)使用共享资源只读,不写的模型
- 不需要写共享资源的模型
- 使用不可变对象
(3)直面线程安全(重点)
- 保证原子性:synchronized
- 保证顺序性:volatile(取消指令重排序)
- 保证可见性:synchronized(上锁时将主内存中的数据同步到工作内存,解锁时将工作内存的数据同步到主内存),volatile(强制从主内存读取数据)
4.小结
基础的线程安全问题以及常见的四种设计模式(单例,生产者&消费者,定时器,线程池)至此全部都介绍完毕了,下节开始就从锁策略开始讲解