手写一个线程池就是这么简单

介绍

为什么要有线程池这个东西?

创建线程对象不像其他对象一样在JVM分配内存即可,还要调用操作系统内核的API,然后操作系统为线程分配一系列的资源,这个成本就很高了。所以线程是一个重量级对象,应该避免频繁创建和销毁

再说一下线程池的大概工作流程

以前我们运行线程的时候new Thread().start()即可,如果线程数多了,频繁的创建线程和销毁线程很费时间。

然后Doug Lea就这样设计了一下,预先启动几个线程,还准备好一个容器。每次想执行任务时,就将实现了Runnable接口的任务放到这个容器中,预先启动好的线程不断从容器中拿出任务,调用执行Runnable接口的run方法,这样刚开始启动的线程就能执行很多次任务。大概流程就是这样,真正的线程池考虑的东西比较多。

想到没有,这就是典型的生产者-消费者模式,线程池的使用者是生产者,线程池本身是消费者。用代码来实现一下

省略try catch版

public class MyThreadPool {

    /** 利用阻塞队列实现生产者-消费者模式 */
    BlockingQueue<Runnable> workQueue;

    /** 保存内部工作线程 */
    List<WorkThread> workThreadList = new ArrayList<>();

    MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
        this.workQueue = workQueue;
        for (int i = 0; i < poolSize; i++) {
            WorkThread workThread = new WorkThread();
            workThread.start();
            workThreadList.add(workThread);
        }
    }

    void execute(Runnable command) {
        // 放入任务,如果没有空间,则阻塞等待
        // try catch部分省略
        workQueue.put(command);
    }


    class WorkThread extends Thread {

        @Override
        public void run() {
            // 循环取任务并执行
            while (true) {
                Runnable task = null;
                // 获取阻塞队列的第一个任务,并删除
                // 如果没有元素,则会阻塞等待
                // try catch部分省略
                task = workQueue.take();
                task.run();
            }
        }
    }

    public static void main(String[] args) {
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5);
        MyThreadPool pool = new MyThreadPool(2, workQueue);
        for (int i = 0; i < 10; i++) {
            int num = i;
            pool.execute(()->{
                System.out.println("线程 " + num + " 执行");
            });
        }
    }

}

可以正常工作

public class MyThreadPool {

    /** 利用阻塞队列实现生产者-消费者模式 */
    BlockingQueue<Runnable> workQueue;

    /** 保存内部工作线程 */
    List<WorkThread> workThreadList = new ArrayList<>();

    MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
        this.workQueue = workQueue;
        for (int i = 0; i < poolSize; i++) {
            WorkThread workThread = new WorkThread();
            workThread.start();
            workThreadList.add(workThread);
        }
    }

    void execute(Runnable command) {
        // 放入任务,如果没有空间,则阻塞等待
        try {
            workQueue.put(command);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    class WorkThread extends Thread {

        @Override
        public void run() {
            // 循环取任务并执行
            while (true) {
                Runnable task = null;
                try {
                    // 获取阻塞队列的第一个任务,并删除
                    // 如果没有元素,则会阻塞等待
                    task = workQueue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                task.run();
            }
        }
    }

    public static void main(String[] args) {
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(5);
        MyThreadPool pool = new MyThreadPool(2, workQueue);
        for (int i = 0; i < 10; i++) {
            int num = i;
            pool.execute(()->{
                System.out.println("线程 " + num + " 执行");
            });
        }
    }

}

来看一下Java中的线程池类ThreadPoolExecutor的构造函数有哪些参数?

public ThreadPoolExecutor
(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

来类比学习一下这些参数,我们把线程池类比为项目组,线程是这个项目组的成员

corePoolSize:线程池中最少的线程数,一个项目组总得有corePoolSize人坚守阵地。

maximumPoolSize:当项目很忙时,就得加人,请其他项目组的人来帮忙。但是公司空间有限,最多只能加到maximumPoolSize个人。当项目闲了,就得撤人了,最多能撤到corePoolSize个人

keepAliveTime & unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?如果一个线程在keepAliveTime(时间数字)* unit(时间单位)时间内都没有执行任务,说明这个线程很闲。如果此时线程数大于corePoolSize,这个线程就要被回收了

workQueue:就是任务队列

threadFactory:自定义如果创建线程,例如给线程指定一个有意义的名字

handler:workQueue满了(排期满了),再提交任务,该怎么处理呢?这个就是处理策略,线程池提供了4种策略,你也可以实现RejectedExecutionHandler接口来自定义策略

实现类 策略
CallerRunsPolicy 提交任务的线程自己去执行该任务
AbortPolicy 默认的拒绝策略,会 throws RejectedExecutionException
DiscardPolicy 直接丢弃任务,没有任何异常抛出
DiscardOldestPolicy 丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列

参考博客

猜你喜欢

转载自blog.csdn.net/zzti_erlie/article/details/91999145
今日推荐