多线程 ---- 线程池

线程的每次启动和销毁都需要消耗很大的性能, 所以引入线程池; 线程池最大的好处就是减少每次启动, 销毁线程的损耗.

(1) 下面以一个送快递的案例具体引入 JDK 的线程池:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadVsThreadPool {

    public static void main(String[] args) {

        // 1. 没有使用线程, 送快递
        // 相当于老板自己送快递, 再干自己的活
        System.out.println("送快递到北京"); // 模拟送快递, 有可能送快递是比较耗时的
        System.out.println("送快递到上海");
        System.out.println("处理自己的业务");

        // 2. 使用手动创建线程的方式, 送快递
        // 相当于雇佣了两个人, 他们在送快递, 此时老板也在干自己的事情
        new Thread(()->{
            System.out.println("送快递到北京");
        }).start();
        new Thread(()->{
            System.out.println("送快递到上海");
        }).start();
        System.out.println("处理自己的业务");

        // 3. 使用 JDK 的线程池来送快递
        // 创建线程池对象, 相当于开了一家送快递的公司, 专门处理送快递的任务
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                4, // 核心线程数: 快递公司的正式员工 ---- 线程
                10, // 最大线程数: 快递公司的总员工(正式工 + 临时工) ---- 线程
                // 临时工 + 空闲时间: 正式员工数量不够处理任务的时候, 招聘临时工, 临时工常超过空闲时间, 就解雇
                60, // 空闲时间数
                TimeUnit.SECONDS, // 时间单位
                new ArrayBlockingQueue<>(1000), // 阻塞队列: 快递公司的仓库, 用来保存快递包裹 ---- 存放线程的容器
                new ThreadFactory() { // 匿名内部类
                    @Override
                    public Thread newThread(Runnable r) { // 线程的工厂类: 快递公司招聘标准 ---- 创建线程的方式
                        return new Thread(r);
                    }
                },
                // 拒绝策略: 接受到新的快递单, 但此时仓库容量不够存放快递包裹
//                new ThreadPoolExecutor.AbortPolicy() // 抛异常的方式:RejectedExecutionException
//                new ThreadPoolExecutor.CallerRunsPolicy() // 谁把包裹交给我的, 让他自己去送(execute 代码所在线程自己执行)
//                new ThreadPoolExecutor.DiscardOldestPolicy() // 把仓库中最旧的包裹丢弃
                new ThreadPoolExecutor.DiscardPolicy() // 把仓库中最新的丢掉
        );
        pool.execute(()->{
            System.out.println("送快递到北京");
        });
        pool.execute(()->{
            System.out.println("送快递到上海");
        });
        System.out.println("处理自己的业务");
    }

}

这里分别使用三种方法实现送快递操作, 分别是没有使用线程, 手动创建线程,
使用 JDK 的线程池来送快递

对线程池的属性加以介绍:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(2) 下面介绍一些简单的线程池

他们都是默认一些属性的值, 方便使用

    ExecutorService pool = Executors.newSingleThreadExecutor();
    ExecutorService pool2 = Executors.newFixedThreadPool(4);
    ExecutorService pool3 = Executors.newScheduledThreadPool(4);
    ExecutorService pool4 = Executors.newCachedThreadPool();

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(3) 使用一下固定大小的线程池(FixedThreadPool)

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

public class runFixedThreadPoolTest {

    private static ExecutorService FIXED_POOL = Executors.newFixedThreadPool(4);

    // 固定大小的线程池
    public static void runFixedThreadPool(Runnable task) {
        FIXED_POOL.execute(task);
    }

    public static void main(String[] args) {
        runFixedThreadPool(()->{
            System.out.println("送快递到北京");
        });
        runFixedThreadPool(()->{
            System.out.println("送快递到上海");
        });
        System.out.println("干自己的事情");
    }
}

(4) 使用一下计划任务线程池(定时任务线程 ScheduledThreadPool)

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolTest {
    private static ScheduledExecutorService SCHEDULED_POOL = Executors.newScheduledThreadPool(4);

    public static void main(String[] args) {
        SCHEDULED_POOL.scheduleAtFixedRate(()->{
            Date date = new Date();
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println(df.format(date));
        }, 1, // 延迟多少秒之后执行一次
                1, // 每间隔多少秒执行一次
                TimeUnit.SECONDS); // 时间单位
        SCHEDULED_POOL.scheduleAtFixedRate(()->{
            System.out.println("abc");
        }, 0, 2, TimeUnit.SECONDS);
    }
}

分析一下这个打印结果:
在这里插入图片描述
在这里插入图片描述
(5) 自己实现一个定时器(定时任务线程池)

import java.util.Date;
import java.util.concurrent.PriorityBlockingQueue;

public class MyScheduledThreadPool {

    //员工
    private MyTimer[] threads; // 这里可以去掉, 直接在构造方法里 new 就可以了

    // 仓库(优先级队列)
    private PriorityBlockingQueue<MyTimerTask> queue = new PriorityBlockingQueue<>();

    public MyScheduledThreadPool(int capacity) {
        threads = new MyTimer[capacity];
        for (int i = 0; i < capacity; i++) {
            threads[i] = new MyTimer(queue);
            threads[i].start();
        }
    }

    // 模拟执行定时任务的方法: 任务, 延迟时间(毫秒), 间隔时间
    public void schedule(Runnable runnable, long delay, long period) {
        MyTimerTask task = new MyTimerTask(runnable, new Date().getTime() + delay, period);
        // 这里考虑到一种特殊情况: 如果有个线程的延迟时间是很久, 另外一个延迟时间短的要先执行, 就会出错, 所以必须使用 notify进行唤醒
        synchronized (queue) {
            queue.put(task);
            queue.notifyAll();
        }
    }

    // 员工
    private static class MyTimer extends Thread {
        private PriorityBlockingQueue<MyTimerTask> queue;

        public MyTimer(PriorityBlockingQueue<MyTimerTask> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    // take 和 put 方法在达到上限和下限的时候, 会阻塞等待
                    // poll 和 offer 是非阻塞方法. 如果达到上限和下限的时候, 返回空
                    MyTimerTask task = queue.take();
                    synchronized (queue) {
                        long current = System.currentTimeMillis(); // 获取当前时间戳
                        if (current < task.next) {
                            // 如果当前时间小于执行下一个的时间, 就等待
                            queue.wait(task.next - current);
                            // 等待直到满足下次执行时间, 需要从仓库中重新获取包裹 ---> 可能有时间更短的任务(更紧急的包裹需要派送)
                            queue.put(task); // 之前已经从仓库取出来在等待了, 要重新放回去, 此处并没有执行
                        } else {
                            task.run();
                            if (task.period > 0) {
                                // 如果时间间隔大于0
                                task.next = task.next + task.period; // 下次执行时间修改为当前的 next + period
                                queue.put(task);
                            }
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 定时任务
    private static class MyTimerTask implements Runnable, Comparable<MyTimerTask> {
        private Runnable runnable;
        private long next; // 下次执行时间
        private long period; // 间隔时间

        public MyTimerTask(Runnable runnable, long next, long period) {
            this.runnable = runnable;
            this.next = next;
            this.period = period;
        }

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

        @Override
        public int compareTo(MyTimerTask o) {
            return Long.compare(next, o.next);
        }
    }

    public static void main(String[] args) {
        MyScheduledThreadPool pool = new MyScheduledThreadPool(4);
        pool.schedule(()->{
            System.out.println("ABC");
        }, 999999999,1000);
        pool.schedule(()->{
            System.out.println("D");
        }, 0,1000);

    }
}

(6) 自己实现一个 JDK 的线程池

package Java05_28;

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

public class MyThreadPool {

    private BlockingQueue<Runnable> queue; // 仓库: 可以使用自定义的阻塞式队列

    // 创建快递公司
    public MyThreadPool(int corePoolSize, int capacity) {
        // 创建仓库
        queue = new ArrayBlockingQueue<>(capacity);
        // 招聘员工
        for (int i = 0; i < corePoolSize; i++) {
            new MyThread(queue).start();
        }
    }

    // 快递公司开放送快递的接口(营业点), 客户调用这个接口(去营业点办业务), 快递仓库存放包裹(往阻塞队列里面加元素)
    private void execute(Runnable task) throws InterruptedException {
        queue.put(task);
    }

    // 正式员工
    private static class MyThread extends Thread{
        private BlockingQueue<Runnable> queue;
        public MyThread(BlockingQueue<Runnable> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                // 员工不停的从仓库取包裹, 没有进入到仓库的就阻塞等待, 取到了就执行
                while (true) {
                    Runnable task = queue.take();
                    task.run();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值