【Java多线程】【定时器和线程池】

1、定时器

 1.1 概念

类似闹钟,达到一个设定的时间后,就执行指定好的代码

  1. 标准库中提供了一个 Timer 类,Timer类的核心方法 schedule
  2. schedule 方法包含两个参数
  • 第一个参数:指定即将执行的任务代码(本质 Runnable )

  • 第二个参数:制定多长时间之后执行
    public static void main(String[] args) {
        Timer t1 = new Timer();
        t1.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("到点了 执行");
            }
        },3000);
        System.out.println("时间快到了 ");
    }

  1. TimerTask 的本质是 Runnable
  2. 里面的方法 靠Timer内部的线程,在时间到了才执行
  3. Timer 内置了线程(前台线程),会组织进程结束
  4. 想结束可以调用 cancel() 方法,如果还有正在执行的定时任务,会等到任务结束才会退出程序
  5. 如果想终止定时任务直接退出,可以purge()来立即清除所有定时任务

 1.2 定时器的实现

定时器,内部管理的不只有一个任务,可以管理多个;

  1. 知道任务的优先级:一个带优先级的阻塞队列(堆)
  2. 队列的每个元素 是一个 Task 任务
  3. 每个任务的元素
    1. 任务的具体执行方法
    2. 任务执行的具体时间

1.2.1 任务 - Task

  1. 任务类,本质就是一个可以根据时间比较的Runnable类
  2. System.currentTimeMillis() 获取当前的时间戳
  3. delay等待的时间,time就是具体的时间戳
  4. 实现Comparable,对时间进行大小比较,因为需要放入优先级队列
  5. 可以将人物类作为Timer的内部类
class MyTask implements Comparable<MyTask>{
    //任务 时间
    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);
    }
}

1.2.2 定时

1. 通过优先级阻塞队列 存储若干个 Task类任务,通过schedule来往队列插入

//核心数据结构 优先级的阻塞队列
private PriorityBlockingQueue<MyTask> bqueue = new PriorityBlockingQueue<>();
 //锁对象
private Object locker = new Object();

 2. 核心接口方法 schedule,用于注册任务,放入优先阻塞队列

    //schedule 方法  delay 是表示 毫秒的时间  (delay时间后执行任务)
    public void schedule(Runnable runnable,long delay) throws InterruptedException {
        //构建任务
        MyTask task = new MyTask(runnable,delay);
        //插入队列
        bqueue.put(task);
        synchronized (locker){
            //唤醒等待
            locker.notify();
        };
    }
3. Timer里有一个内置线程,在构造函数,对时间是否到进行判定是否执行时间;没到进行计算还需等待多少时间, 进入等待时间,避免进行循环忙等问题
 //构造函数
    public MyTimer(){
        //构造线程
        Thread thread = new Thread(()->{
            while (true){
                try {
                    //1.取出队头元素
                    MyTask myTask = bqueue.take();
                    //2.取到当前时间
                    long newTime = System.currentTimeMillis();
                    //3.判断时间是否到了
                    if(myTask.time <= newTime){
                        //4.时间到了执行任务
                        myTask.runnable.run();
                    }else {
                        //5.时间没到,放回队列
                        //问题:要确定 时间没到 不要忙等,循环取放
                        bqueue.put(myTask);
                        //等待相差时间
                        synchronized (locker) {
                            locker.wait( myTask.time-newTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动线程
        thread.start();
    }

这几步写完了,但是没有问题吗,线程是否安全?加锁的位置正确吗?还能改进吗?

问题:14:30进入等待之前,14:10唤醒空大,14:30进入等待,14:10未能将线程唤醒,不会进入新的等待,直到等到14:30,; 

 解决:将时间判定执行的过程一块加锁,保证原子性,上一个未进入等待,新线程不会唤醒

public MyTimer(){
        //构造线程
        Thread thread = new Thread(()->{
            while (true){
                try {
                    //等待 要 加锁
                    synchronized (locker) {
                        //1.取出队头元素
                        MyTask myTask = bqueue.take();
                        //2.取到当前时间
                        long newTime = System.currentTimeMillis();
                        //3.判断时间是否到了
                        if(myTask.time <= newTime){
                            //4.时间到了执行任务
                            myTask.runnable.run();
                        }else {
                            //5.时间没到,放回队列
                            //问题:要确定 时间没到 不要忙等,循环取放
                            bqueue.put(myTask);
                            //等待相差时间
                            locker.wait( myTask.time-newTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动线程
        thread.start();
    }

1.3.3 测试代码

public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer = new MyTimer();
        //安排任务
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("倒计时:1");
            }
        },5000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("倒计时:2");
            }
        },4000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("倒计时:3");
            }
        },3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("倒计时:4");
            }
        },2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("倒计时:5");
            }
        },1000);

        System.out.println("火箭发射倒计时:");
    }

 2、线程池

2.1 概念

线程池是什么?

  1. 线程池是一种线程管理机制
  2. 主要作用:减少线程创建和销毁的开销,避免频繁创建和销毁线程所带来的性能损失和资源浪费;
  3. 在java中,线程池是由ThreadPoolExecutor类实现的;

为什么使用线程池?

从线程池里拿线程比从系统创建线程更高效

  1. 从线程池拿线程,是纯粹用户态操作
  2. 从系统创建线程,涉及到用户态和内核之间的切换,真正的创建是在内核完成的

内核态:操作系统运行时所处权限下的运行模式,此时操作系统内核具备有完全的控制权,可以访问系统的所有资源;

在系统调用时进程需要从用户态切换为用户态,以便访问内核下的系统资源和执行特权操作,这种切换是有一定的时间和开销;

操作系统 = 内核 + 配套的应用程序

应用程序同一时间有很多,交给操作系统创建,可能不是很及时,具体由系统决定

(就如去窗口提交(操作系统)办证件,什么办下来是工作人员(CPU)决定,切换为工作人员才能使用窗口内的设备,时间不可控

用户态:进程运行时所处的普通权限下的运行模式,此时进程只能访问自己的用户空间,不能直接访问系统的;

用户在要开开辟的进程中,设定好要开辟线程,再提交给系统开辟进程(批准资源)

(证件资料什么用户已经准备好,只需要工作人员给盖章批准,时间可控

 2.2 线程池的创建

2.2.1 标准池中的线程池

java线程池是基于Executor框架实现的,能够提供高效的线程池管理和任务执行功能

Executor中创建线程池的方式:

  1. newFixedThreadPool(): 创建固定线程数的线程池
  2. newCachedThreadPool(): 创建线程数目动态增长的线程池.
  3. newSingleThreadExecutor(): 创建只包含单个线程的线程池.
  4. newScheduledThreadPool(): 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
public static void main(String[] args) {
    //固定数目
    ExecutorService pool1 = Executors.newFixedThreadPool(10);
    //动态增长
    ExecutorService pool2 = Executors.newCachedThreadPool();
    //单个线程
    ExecutorService pool3 = Executors.newSingleThreadExecutor();
    //延迟执行
    ExecutorService pool4 = Executors.newScheduledThreadPool(1000);
    //提交任务到线程池
    pool1.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println("注册任务到线程池");
        }
    });
}
  1. 通过 Executor 类将线程池构建出来
  2. Executors 本质上是 ThreadPoolExecutor 类的封装.
  3. 线程池返回值类型:ExecutorService;这是Executor框架提供的一个接口,用于管理和执行线程池中的人物

2.2.2 工厂模式

上述创建线程池的方法,采用的工厂模式,这是对构造方法不足的补充

例如,不同构造方法类型、数量、顺序要不一样,但是都一样,就构造方法执行不一样怎么办;

构造坐标系,有xy轴坐标系,也有极坐标坐标系,两个参数都一样,但是要区分;

工厂模式就是创建对象的过程中,通过将对象的创建过程封装在一个工厂类中,使得对象的创建过程更加灵活和可控。

2.2.3 构造方法参数

 线程池的类、方法都在java.util.concurrent 这个包里,简称JUC (并发) 

这是ThreadPoolExecutor的其中参数最全的一个构造方法,参数的含义:

1.  corePoolSize (int):核心线程数(基本大小>=0) —— 正式员工

2.  maximumPoolSize (int):最大线程数(线程池大小>=1) —— 正式+实习

线程数的多少取决于系统许运行的任务,多就加些实习,不多就不加,辞退实习

3.  keepAliveTime (long):线程存活的时间(>=0)

4.  unit :存活时间的单位(s、ms)

5.  workQueue :线程池的任务是有阻塞队列来组织的(不能为空)

6.  threadFactory:工厂模式,创建线程的辅助的类

7.  handler:线程池的拒绝策略(池子满了,继续添加任务,如何拒绝)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,

long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,

ThreadFactory threadFactory, RejectedExecutionHandler handler)

 2.2.4 线程池拒绝策略

线程池的拒绝策略是当前线程池的任务满了(阻塞队列满了),并且线程池中的线程数达到最大值,新的任务无法继续提交时,线程池采用的处理方式。

 Java标准库中的ThreadPoolExecutor类提供了四种线程池拒绝策略:

拒绝策略

功能

AbortPolicy

默认的拒绝策略,当任务无法处理,直接抛出RejectedExecutionException异常

CallerRunsPolicy

任务无法处理,在调用者的线程中直接执行该任务

DiscardOldestPolicy

丢弃最老的任务(队头),尝试将新任务添加到队列

DiscardPolicy

丢弃最新的任务(队尾),直接丢弃该任务,不做处理

class MyThreadPool {
    //阻塞队列 存储任务
    private BlockingQueue<Runnable> bq = new LinkedBlockingQueue<>();

    //sumbit 提交任务
    public void sumbit(Runnable runnable) throws InterruptedException {
        //添加任务
        bq.put(runnable);
    }

    //线程池构造、调度
    public MyThreadPool(int n) {
        //最多 n 个线程加入
        for (int i = 0; i < n; i++) {
            Thread thread = new Thread(() -> {
                //循环取取任务
                try {
                    while (true) {
                        Runnable runnable = bq.take();
                        //运行
                        runnable.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
    }
}
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool = new MyThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            int num = i;
            myThreadPool.sumbit(()->{
                System.out.println("执行:"+num);
            });
        }

    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值