线程池知识梳理

一、为什么要有线程池?

启动线程去做任务可以发挥多核CPU的优势,提高程序执行性能。但频繁的创建、销毁线程对象又会导致整体系统的执行效率不高,甚至出现严重问题。所以要引入“池化”概念。预先准备一些资源在池子中,有需要时就从池子中获取资源;如果资源已经被用光,再有请求过来则需要等待资源被释放后才能使用空闲的资源。达到重复利用,提高整体性能。

二、线程池做了什么?

线程池核心工作过程:

1、初始化线程池,指定线程池的大小
2、向线程池中放入任务执行
3、如果线程池中创建的线程数目未达到指定大小,则创建我们自定义的线程类放入线程池集合,并立刻执行任务。执行完毕后该线程会一直监听队列
4、如果线程池中创建的线程数目已满,则将任务放入阻塞缓存队列中(阻塞队列保证队列的执行顺序,不会有并发问题)
5、线程池中所有创建的线程,都会一直从缓存队列中取任务,取到任务立马执行

三、如何自行实现一个线程池?

public class CustomThreadPool {

    // 存放线程的集合
    private ArrayList threads;

    // 任务队列
    private ArrayBlockingQueue taskQueue;

    // 线程初始化限定大小
    private int threadNum;

    // 已经工作的线程数目
    private int workThreadNum;

    private final ReentrantLock mainLock = new ReentrantLock();

    public CustomThreadPool(int initPoolNum) {
        threadNum = initPoolNum;
        threads = new ArrayList<>(initPoolNum);

        // 任务队列初始化为线程池线程数的四倍
        taskQueue = new ArrayBlockingQueue(initPoolNum * 4);

        workThreadNum = 0;
    }

    public void execute(Runnable runnable) {
        try {
            mainLock.lock();

            // 线程池未满,每加入一个任务开启一个线程
            if (workThreadNum < threadNum) {
                CustomThread customThread = new CustomThread(runnable);
                customThread.start();
                threads.add(customThread);
                workThreadNum++;
            } else { // 线程池已满,放入任务队列,等待空闲线程执行
                // 队列已满,无法再添加任务,则会拒绝这个任务
                // offer 的作用,在不超出队列长度的情况下在队列尾部插入元素,如果成功则返回true,如果失败则返回false
                if (!taskQueue.offer(runnable)) {
                    rejectTask();
                }
            }
        } finally {
            mainLock.unlock();
        }
    }

    private void rejectTask() {
        System.out.println("任务队列已满,无法再添加,请扩大你的初始线程数量");
    }


    class CustomThread extends Thread {

        private Runnable task;

        public CustomThread(Runnable runnable) {
            this.task = runnable;
        }

        @Override
        public void run() {
            super.run();
            // 该线程一直启动,不断从任务队列中取出任务执行
            while (true) {
                // 如果初始化任务不为空,则直接执行初始化任务
                if (task != null) {
                    task.run();
                    task = null;
                } else { // 如果没有初始化任务,则从任务队列中获取任务并执行
                    Runnable queueTask = taskQueue.poll();
                    if (queueTask != null) {
                        queueTask.run();
                    }
                }
            }
        }
    }

}

测试过程

       CustomThreadPool pool = new CustomThreadPool(5);

        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "   执行中");
            }
        };

        for (int i = 0; i < 20; i++) {
            pool.execute(task);
        }

简单总结:
在不断添加任务的过程中,会优先启动核心线程,直到核心线程数达到5,这5个核心线程会一直在后面运行,处理完自己携带的任务后,就会继续从阻塞队列中获取缓存任务,由于是阻塞队列,一个线程在获取任务的过程中,其他线程会等待这个线程获取成功后再获取任务。获取完任务后就会执行,然后线程进行下一次的轮询继续获取任务并进行执行。说明这5条核心线程对象是会一直存在,直到系统退出。

你可能感兴趣的:(线程池知识梳理)