JavaEE初阶Day 12:多线程(10)

Day 12:多线程(10)

线程池

1. 标准库线程池(ThreadPoolExecutor)

构造方法比较复杂

(1)核心线程数/最大线程数:管理策略

(2)存活时间/时间单位:非核心线程允许空闲的最大时间

(3)任务队列BlockingQueue:存放了线程池要执行的任务

(4)线程工厂:辅助创建线程的工厂类(工厂模式)

(5)拒绝策略:任务队列满了,接下来继续submit,线程池要处理

2. 拒绝策略

理解过程和效果

  • 直接抛出异常
  • 由调用者负责执行:submit内部要做的事情不仅仅是入队列,如果发现队列满了并且使用当前这个策略,就会在submit内部自己去执行Runnable的run方法
  • 丢弃最早的任务:任务队列中队首的元素舍弃掉
  • 丢弃最新的任务:正在submit这个任务就不要了

实现拒绝策略的核心,在submit这里

  • 构造方法里提供参数,用参数来表示哪种拒绝策略,使用成员变量记录一下
  • submit的时候,先判定当前任务队列元素是否已经比较多了(自行设定一个阈值
  • 如果确实比较多了,就根据刚才构造时指定的拒绝策略,执行不同的逻辑

3. 标准库提供的创建线程池的工厂方法

由于标准库ThreadPoolExecutor使用起来比较费劲,于是标准库提供了几个工厂类,对于上述线程池又进一步封装了

package thread;

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

public class Demo34 {
    public static void main(String[] args) {

        //创建固定线程数目的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10000; i++) {
            int id = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello " + id + "," + Thread.currentThread().getName());
                }
            });
        }
        
        //创建一个最普通的线程池,能够根据任务的数目,自动进行线程扩容
        Executors.newCachedThreadPool();
        //创建一个只包含单个线程的线程池
        Executors.newSingleThreadExecutor();
        //创建一个固定线程个数,但是任务延时执行的线程池
        Executors.newScheduledThreadPool(4);
    }
}

当使用线程池的时候,main执行结束,进程没有结束,主要是因为线程池中的线程是前台线程,会阻止进程结束

4. 线程池实现

一个线程池要包含什么

  • 有若干个线程
  • 有任务队列(使用Runnable即可)
  • 提供submit方法
package thread;

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

// 写一个比较简单的线程池.
class MyThreadPool {
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
    private int maxPoolSize = 0;
    private List<Thread> threadList = new ArrayList<>();

    // 初始化线程池 (FixedThreadPool)
    public MyThreadPool(int corePoolSize, int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
        // 创建若干个线程
        for (int i = 0; i < corePoolSize; i++) {
            Thread t = new Thread(() -> {
                try {
                    while (true) {
                        Runnable runnable = queue.take();
                        runnable.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
            threadList.add(t);
        }
    }

    // 把任务添加到线程池中
    void submit(Runnable runnable) throws InterruptedException {
        // 此处进行判定, 判定说当前任务队列的元素个数, 是否比较长.
        // 如果队列元素比较长, 说明已有的线程, 不太能处理过来了. 创建新的线程即可.
        // 如果队列不是很长, 没必要创建新的线程.
        queue.put(runnable);

        // 这里的 阈值 都是咱们拍脑门想的.
        if (queue.size() >= 500 && threadList.size() < maxPoolSize) {
            // 创建新的线程即可
            Thread t = new Thread(() -> {
                try {
                    while (true) {
                        Runnable task = queue.take();
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    }
}

public class Demo35 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool threadPool = new MyThreadPool(10, 20);

        for (int i = 0; i < 10000; i++) {
            int id = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello " + id + ", " + Thread.currentThread().getName());
                }
            });
        }
    }
}

上述代码并没有包含回收非核心线程,回收非核心线程需要引入更多的数据结构,也需要引入“定时器”概念

定时器

定时器:就是闹钟的效果,指定一个任务(Runnable),并且指定一个时间(3000ms),此时这个任务不会立即执行,而是在时间到达之后,再去执行,也就是所谓的定时执行/延时执行

定时器是日常开发中非常重要的基础组件,例如短信验证,验证码是有时效的,这样的效果就可以使用定时器,发送短信验证码的时候,生成验证码,保存起来,设定定时器,就会在5分钟之后,执行一个任务,删除刚才这个验证码

正因为定时器非常重要,未来实际开发中,甚至会把定时器功能单独封装成服务器,供整个分布式系统来使用

1. Timer

Timer是Java标准库中的定时器

package thread;

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

public class Demo36 {
    public static void main(String[] args) {

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        },3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        },2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 1000");
            }
        },1000);


    }
}
  • timer.schedule:把任务在合理的时间安排好
  • TimerTask:schedule的第一个参数,是一个抽象类,继承了Runnable
  • delay:schedule的第二个参数,表示“多长时间后”执行,以当前执行schedule的时刻为基准,继续等delay时间之后再去进一步执行
  • Timer内部包含了前台线程,阻止了进程结束
  • Timer里面内部包含了线程,把线程创建都封装起来了

2. Timer代码实现

2.1 代码需求
  • 能够延时执行任务/指定时间执行任务
  • 能够管理多个任务
2.2 代码实现
  • 定义一个MyTimerTask类,包含任务(Runnable)与延时时间(delay)
    • 首先,该类的成员变量包含Runnable runnablelong timetime为绝对时间,等于系统当前时间加上delay
    • 其次,构造方法中,构造上述两个成员变量
    • 然后,成员方法提供两个:void run()用于执行runnable任务;int getTime()返回time该任务执行的时间
    • 最后,重写compareTo,为后续优先级队列提供比较对象
  • 定义一个MyTimer类
    • 首先,定义一个优先级队列,元素类型MyTimerTask类,通过优先级队列将这些任务保存起来,按照时间作为优先级的先后标准,可以做到,队首元素就是时间最靠前的任务
    • 然后,在构造方法中,定义一个线程,用于循环扫描上述队列,扫描线程只需要关注队首元素即可,时间最靠前的任务都还没有到点,剩下的任务更不会到点了
    • 最后,定义成员方法void schedule(Runnable runnable, long delay),第一个参数表示任务,第二个参数表示延时时间,将该任务保存到优先级队列当中

【注意】

(1)扫描线程中,获取到队首元素,判定是否到时间,到时间就执行,并且出队列,没到时间,就继续下一次循环

(2)引入锁,把针对队列的操作,都加锁了

(3)解决忙等问题,引入wait和notify:队列为空要wait(死等);队首元素没到时间,wait(带有超时时间);添加了新的元素到队列中后,需要notify

这里不能使用sleep:sleep通过interrupt唤醒是非常规手段;sleep不会释放锁,会影响后续插入任务

(4)引入比较规则,让MyTimerTask可以按照时间先后来制定优先级

package thread;


import java.util.PriorityQueue;

class MyTimerTask implements Comparable<MyTimerTask> {
    private Runnable runnable;
    //什么时候去执行,此处的time不是delay,而是ms级别的时间戳(绝对时间)
    private long time;

    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    public void run() {
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        //按照时间来进行比较,期望最终构造出小根堆
        return (int)(this.time - o.time);
    }
}

class MyTimer {
    private PriorityQueue<MyTimerTask> taskQueue = new PriorityQueue<>();

    private Object locker = new Object();

    public MyTimer() {
        //这个线程负责不停的扫描上述的队列队首元素,来确定是否要执行任务
        Thread t = new Thread(()->{
            try {
                while (true) {
                    synchronized (locker){
                        if (taskQueue.size() == 0){
                            locker.wait();
                        }

                        MyTimerTask task = taskQueue.peek();
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            //时间到了,要执行任务
                            task.run();
                            taskQueue.poll();
                        }else {
                            //时间没到
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                }

            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });
        t.start();
    }

    public void schedule (Runnable runnable, long delay) {
        synchronized (locker){
            MyTimerTask task = new MyTimerTask(runnable, delay);
            taskQueue.offer(task);
            locker.notify();
        }
    }
}
public class Demo37 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        },3000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        },2000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 1000");
            }
        },1000);
    }
}

上述是一种典型的定时器的实现,除了这种基于优先级队列之外,还有一种典型的实现,基于“时间轮“的方式。

时间轮反而更简单一点,类似于带环的链表,每个节点表示一个时间单位,每个节点上再管理一系列的任务,有一个线程每隔固定时间,遍历到下一个节点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

胖了你都蹲不下来撸猫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值