Java 中的线程池是怎样实现的?

作者:bravo1988
链接:https://www.zhihu.com/question/558457760/answer/2708771354
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

要了解线程池,必须先了解线程。

线程的简单概念

在Java中需要区分“线程”的两个不同维度的概念:

  • thread
  • Thread Java类/对象

thread

第一个thread,指的是实际意义上的“线程”,通常定义是:CPU的一条执行路径。线程(英语:thread)是cpu进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

作为非科班,我往往会望文生义,线程、线程,那么就应该像一条线,串联起我们的程序。虽然说不上错,但其实不够准确。我现在对线程的理解是,线程就是CPU的一条执行路径。

什么叫 CPU的一条执行路径呢?以单核CPU的计算为例,CPU在同一时刻实际上只能执行一个程序。那么为什么单核计算机上却可以同时听歌、敲代码呢?

这里就涉及到一个所谓的“时钟中断”和“时间分片”的概念。

CPU就像一头驴,一刻不停地在工作,而晶振片就像一把苍蝇拍,按照一定频率抽打CPU。

把左右两边的水龙头看做两个程序,也可以看做我们项目里的method1和method2。CPU在同一时刻只能执行其中一方,但通过晶振片,可以把CPU的执行时间打散成一个个小片段,叫做 时间分片,在每个时间段执行的代码路径就像上图的折线,一会儿执行左边的,一会儿执行右边的。

Thread类/对象

我们作为人类自然是无法直接拿着苍蝇拍打CPU的,也无法直接操作晶振片,我们需要另一层抽象:操作系统。操作系统负责调度每一段时间片,在不同的时间片去处理不同的程序,也就产生了多线程。

现在大概理解了操作系统和CPU层面的线程,那么JVM中的线程是什么概念呢?两者有什么关联吗?

首先要明白,JAVA作为一门编程语言,是没有产生线程的能力的。语言只能产生命令,却不能对现实产生实际的作用,它最终还是要依赖于实际媒介。就好比我问你,要不要吹空调。如果你说要,那我会找到遥控器打开空调,却不是自己吹出冷风。

所以JVM的线程是从操作系统借的。

Java中有个Thread类,可以得到Thread对象,通过Thread#start(),可以让JVM帮我们向操作系统借“一条线程”,也就是分配出一条执行路径,执行我们指定的代码。

那么就会涉及到一个问题:怎么指定?会不会好不容易借过来thread,结果执行的代码不是我们期望的?于是就会衍生出一个绑定关系:任务和线程如何绑定?绑定后thread就能执行指定任务。

也就是说,Java的这套Thread API本质上就是提供一种绑定关系,让线程和任务得以绑定。通俗点理解,Java的Thread就像张天师手中的天雷符,天雷符不是真正的天雷,但却可以向老天爷(操作系统)借一道天雷。

线程池逻辑

由于时间有限,马上要上班了。线程池部分先简单介绍一下思路。

上面以天雷比喻线程,那么我想问各位一句:如何复用一道天雷?好不容易向老天接了一道天雷,总不至于劈一次就结束吧,毕竟世间魑魅魍魉何其多!所以就要考虑复用天雷!而线程池就是为了复用天雷。

如何不让天雷消失?就是告诉它:魑魅魍魉除尽前,都不得消失!

所以线程池的复用逻辑很简单粗暴:如果任务没执行完,thread就不会消失。即使任务执行完了,我也要造成一种假象,让CPU觉得还没执行完,即阻塞线程!

总结一下线程池复用线程的核心逻辑:

  • 持续执行任务
  • 即使任务执行结束,也可以通过阻塞线程,不让线程结束,最终提为“线程复用

简单版线程池

public class SimpleThreadPool {

    /**
     * 任务队列
     */
    BlockingQueue<Runnable> workQueue;

    /**
     * 工作线程
     */
    List<Worker> workers = new ArrayList<>();

    /**
     * 构造器
     *
     * @param poolSize  线程数
     * @param workQueue 任务队列
     */
    SimpleThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
        this.workQueue = workQueue;
        // 创建线程,并加入线程池
        for (int i = 0; i < poolSize; i++) {
            Worker work = new Worker();
            work.start();
            workers.add(work);
        }
    }

    /**
     * 提交任务
     *
     * @param command
     */
    void execute(Runnable command) {
        try {
            // 任务队列满了则阻塞
            workQueue.put(command);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 工作线程,负责执行任务
     */
    class Worker extends Thread {
        public void run() {
            // 循环获取任务,如果任务为空则阻塞等待
            while (true) {
                try {
                    Runnable task = workQueue.take();
                    task.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

复杂版线程池 

作者:bravo1988
链接:https://www.zhihu.com/question/558457760/answer/2708771354
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

public class ThreadPool {

    private final ReentrantLock mainLock = new ReentrantLock();

    /**
     * 工作线程
     */
    private final List<Worker> workers = new ArrayList<>();
    /**
     * 任务队列
     */
    private BlockingQueue<Runnable> workQueue;
    /**
     * 核心线程数
     */
    private final int corePoolSize;
    /**
     * 最大线程数
     */
    private final int maximumPoolSize;
    /**
     * 非核心线程最大空闲时间(否则销毁线程)
     */
    private final long keepAliveTime;

    public ThreadPool(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit timeUnit,
                      BlockingQueue<Runnable> workQueue) {
        this.workQueue = workQueue;
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.keepAliveTime = timeUnit.toNanos(keepAliveTime);
    }

    public void execute(Runnable task) {
        Assert.notNull(task, "task is null");

        // 创建核心线程处理任务
        if (workers.size() < corePoolSize) {
            this.addWorker(task, true);
            return;
        }

        // 尝试加入任务队列
        boolean enqueued = workQueue.offer(task);
        if (enqueued) {
            return;
        }

        // 创建非核心线程处理任务
        if (!this.addWorker(task, false)) {
            // 非核心线程数达到上限,触发拒绝策略
            throw new RuntimeException("拒绝策略");
        }
    }

    private boolean addWorker(Runnable task, boolean core) {
        int wc = workers.size();
        if (wc >= (core ? corePoolSize : maximumPoolSize)) {
            return false;
        }

        boolean workerStarted = false;
        try {
            Worker worker = new Worker(task);
            final Thread thread = worker.getThread();
            if (thread != null) {
                mainLock.lock();
                workers.add(worker);
                thread.start();
                workerStarted = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            mainLock.unlock();
        }

        return workerStarted;
    }

    private void runWorker(Worker worker) {
        Runnable task = worker.getTask();

        try {
            // 循环处理任务
            while (task != null || (task = getTask()) != null) {
                task.run();
                task = null;
            }
        } finally {
            // 从循环退出来,意味着当前线程是非核心线程,而且需要被销毁
            // Java的线程,既可以指代Thread对象,也可以指代JVM线程,一个Thread对象绑定一个JVM线程
            // 因此,线程的销毁分为两个维度:1.把Thread对象从workers移除 2.JVM线程执行完当前任务,会自然销毁
            workers.remove(worker); // 这里前后应该加锁,否则线程不安全。由于是demo,很多处理比较随意
        }
    }


    private Runnable getTask() {
        boolean timedOut = false;

        // 循环获取任务
        for (; ; ) {

            // 是否需要检测超时:当前线程数超过核心线程
            boolean timed = workers.size() > corePoolSize;

            // 需要检测超时 && 已经超时了
            if (timed && timedOut) {
                return null;
            }

            try {
                // 是否需要检测超时
                // 1.需要:poll阻塞获取,等待keepAliveTime,等待结束就返回,不管有没有获取到任务
                // 2.不需要:take持续阻塞,直到获取结果
                Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

    @Getter
    @Setter
    private class Worker implements Runnable {
        private Thread thread;
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
            thread = new Thread(this);
        }

        @Override
        public void run() {
            runWorker(this);
        }
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值