深入理解并发编程-ThreadPool

本为为读书笔记,书籍为java并发编程的艺术(ThreadPool非jdk1.8)
参考:https://www.cnblogs.com/leesf456/p/5585627.html
https://blog.csdn.net/programmer_at/article/details/79799267

1.线程池基本概念

1.1 使用线程池的好处:

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

1.2 线程池的实现原理

1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。

2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

1.3 处理流程(execute方法)

在这里插入图片描述
在这里插入图片描述

2.手写线程池

  • 需要一个阻塞队列
  • 执行任务的工作线程

2.1 代码

class ThreadPool {
    private BlockingQueue<Runnable> taskQueue;

    private HashSet<Worker> workers = new HashSet<>();
    //核心线程数
    private int coreSize;
    //获取任务的超时时间
    private long timeout;

    private TimeUnit timeUnit;

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapacity);
    }

    public void execute(Runnable task) {
        synchronized (workers) {
            if (workers.size() < coreSize) {

                Worker worker = new Worker(task);
                System.out.println("新增任务" + worker + task);
                worker.start();
                workers.add(worker);
            } else {
                System.out.println("新增任务进阻塞队列" + task);
                taskQueue.put(task);
            }
        }
    }

    class Worker extends Thread {
        private Runnable task;

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


        public void run2() {
            //执行任务 worker 结束 就说明阻塞队列里也没有任务了 就会一直停在task处,因为task是非超时阻塞
            //就阻塞住了 ,如果使用poll则是非超时阻塞
            while (task != null || (task = taskQueue.take()) != null) {
                try {
                    System.out.println(Thread.currentThread().getName() + "执行任务" + task);
                    task.run();
                } catch (Exception e) {

                } finally {
                    task = null;
                }
            }
            synchronized (workers) {
                System.out.println(this + "worker被移除");
                workers.remove(this);
            }
        }


        public void run() {
            //执行任务 worker 结束 就说明阻塞队列里也没有任务了 就会一直挺再take处,因为take是非超时阻塞
            //就阻塞住了 ,如果使用poll则是非超时阻塞
            while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
                try {
                    System.out.println("poll");
                    System.out.println(Thread.currentThread().getName() + "执行任务" + task);
                    task.run();
                } catch (Exception e) {

                } finally {
                    task = null;
                }
            }
            synchronized (workers) {
                System.out.println(this + "worker被移除");
                workers.remove(this);
            }
        }
    }
}

class BlockingQueue<T> {

    //任务队列
    private Deque<T> queue = new ArrayDeque<>();
    private ReentrantLock lock = new ReentrantLock();
    //生产者条件变量
    private Condition fullWaitSet = lock.newCondition();
    //消费者条件变量
    private Condition emptyWaitSet = lock.newCondition();
    //容量
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    /**
     * 带超时的阻塞获取
     *
     * @return
     */
    public T poll(long timeout, TimeUnit timeUnit) {
        lock.lock();
        try {
            long nanos = timeUnit.toNanos(timeout);
            while (queue.isEmpty()) {
                try {
                    if (nanos <= 0) {
                        return null;
                    }
                    nanos = emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    public T take() {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    public void put(T element) {
        lock.lock();
        try {

            while (queue.size() == capacity) {
                try {
                    System.out.println("等待假如阻塞队列" + element);
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            queue.addLast(element);
            emptyWaitSet.signal();
        } finally {
            lock.unlock();
        }
    }

    public int size() {
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }

2.2增加拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略
待定

3.线程池源码(注意源码里面的注释)

主池控制状态ctl是一个原子整数包装两个概念领域

  • workerCount,表示线程的有效数量
  • runState,指示是否运行、关闭等

谨记高三位为状态位,而只有五种状态,是否就提供了扩展性呢?

补一下位运算知识吧:
在这里插入图片描述

  private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //线程池状态码
    private static final int COUNT_BITS = Integer.SIZE - 3;
 	//0001 1111 1111 1111 1111 1111 1111 1111
	private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
 
    // runState is stored in the high-order bits
    //可以看出状态码是-1 0 1 2 3 位移29位
    //可以看出running的状态小于0 -1位移29位为负整数,每次添加一个线程,做++操作。
   //1110 0000 0000 0000 0000 0000 0000 0000 
	//能接受新任务,队列中的任务可继续运行
	private static final int RUNNING    = -1 << COUNT_BITS;
	//0000 0000 0000 0000 0000 0000 0000 0000
	//不再接受新任务,队列中的任务仍可继续执行
	private static final int SHUTDOWN   =  0 << COUNT_BITS;
	//0010 0000 0000 0000 0000 0000 0000 0000
	//不再接受新任务,不再执行队列中的任务,中断所有执行中的任务(发中断消息)
	private static final int STOP       =  1 << COUNT_BITS;
	//0100 0000 0000 0000 0000 0000 0000 0000
	//所有任务均已终止,workerCount的值为0,转到TIDYING状态的线程即将要执行terminated()钩子方法.
	private static final int TIDYING    =  2 << COUNT_BITS;
	//0110 0000 0000 0000 0000 0000 0000 0000
	//terminated()方法执行结束
	private static final int TERMINATED =  3 << COUNT_BITS;
    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }
//初始化线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//获取当前线程的运行状态 屏蔽高三位,只有低29位参与运算
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//获取当前的线程数 屏蔽高三位,只有低29位参与运算
private static int workerCountOf(int c)  { return c & CAPACITY; }
//或操作
private static int ctlOf(int rs, int wc) { return rs | wc; }

我们先看5个状态,只看最高的3位,分别是:

RUNNING    = 111
SHUTDOWN   = 000
STOP       = 001
TIDYING    = 010
TERMINATED = 011
//大小关系:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
//所以存在这样的判断方法:
 private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }
private static boolean runStateLessThan(int c, int s) {
        return c < s;
    }

private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }
  • RUNNING:正在处理任务和接受队列中的任务。
  • SHUTDOWN:不再接受新的任务,但是会继续处理完队列中的任务。
  • STOP:不再接受新任务,也不继续处理队列中的任务,并且会中止正在处理的任务。
  • TIDYING:所有任务都已经处理结束,目前worker数为0,当线程池进入这个状态的时候,会调用terminated()方法。
  • TERMINATED:线程池已经全部结束,并且terminated()方法执行完成。

其他重要参数:

//待执行线程队列
private final BlockingQueue<Runnable> workQueue;
//锁,基于重入锁,线程池核心之一
private final ReentrantLock mainLock = new ReentrantLock();
//线程队列,这是该线程池内已有线程
//注意与workQueue的区别
private final HashSet<Worker> workers = new HashSet<Worker>();
//多线程协调通信
private final Condition termination = mainLock.newCondition();
//拒绝handler,用于线程池不接受新加线程时的处理方式
//分为系统拒绝(线程池要关闭等),与线程池饱和(已达线程池最大容量)
private volatile RejectedExecutionHandler handler;
//线程工厂,新建线程池时带入
private volatile ThreadFactory threadFactory;
//默认拒绝向线程池中新加线程的方式:丢弃
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();

3.1 execute()

注释翻译:

1.如果运行的线程小于corePoolSize,则尝试用给定的命令启动一个新线程任务。对addWorker的调用会自动地检查runState和 workerCount,从而通过返回false来防止在不应该添加线程的情况下添加线程的错误警报。

2.如果一个任务可以成功地进入队列,那么我们仍然需要来再次检查是否应该添加一个线程(因为现有的线程在最后一次检查后死亡),或者池在进入这个方法后关闭。因此,我们重新检查状态,如果有必要,如果停止,则回滚队列;如果没有,则启动一个新线程。

3.如果我们不能队列任务,然后我们尝试添加一个新的线程。如果它失败了,我们知道我们被关闭或饱和,所以拒绝任务。

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
       	//
           int c = ctl.get();
        //工作线程小于corePoolSize
        if (workerCountOf(c) < corePoolSize) {
            //添加一个core线程,此处参数为true,表示添加的线程是core容量下的线程
            if (addWorker(command, true))
                return;
            //刷新数据,乐观锁就是没有锁
            c = ctl.get();
        }
        //判断线程池运行状态,工作队列是否有空间
        if (isRunning(c) && workQueue.offer(command)) {
            //再次检测
            int recheck = ctl.get();
            //线程池已停止,就移除队列
            if (! isRunning(recheck) && remove(command))
                //执行拒绝策略
                reject(command);
            //线程池在运行,有效线程数为0 
            else if (workerCountOf(recheck) == 0)
                //添加一个空线程进线程池,使用非core容量线程
                //仅有一种情况,会走这步,core线程数为0,max线程数>0,队列容量>0
                //创建一个非core容量的线程,线程池会将队列的command执行
                addWorker(null, false);
        }
        //线程池停止了或者队列已满,添加maximumPoolSize容量工作线程,如果失败,执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
}

3.2 addWorker()

说明:此函数可能会完成如下几件任务

① 原子性的增加workerCount。

② 将用户给定的任务封装成为一个worker,并将此worker添加进workers集合中。

③ 启动worker对应的线程,并启动该线程,运行worker的run方法。

④ 回滚worker的创建动作,即将worker从workers集合中删除,并原子性的减少workerCount。

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        //我比较喜欢叫线程里的死循环叫自旋
        for (;;) { // 外层无限循环
            // 获取线程池控制状态
            int c = ctl.get();
            // 获取状态
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&            // 状态大于等于SHUTDOWN,初始的ctl为RUNNING,小于SHUTDOWN
                ! (rs == SHUTDOWN &&        // 状态为SHUTDOWN
                   firstTask == null &&        // 第一个任务为null
                   ! workQueue.isEmpty()))     // worker队列不为空
                // 返回
                return false;
			
            for (;;) {
                // worker数量
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||                                // worker数量大于等于最大容量
                    wc >= (core ? corePoolSize : maximumPoolSize))    // worker数量大于等于核心线程池大小或者最大线程池大小
                    return false;
                if (compareAndIncrementWorkerCount(c))                 // 比较并增加worker的数量
                    // 跳出外层循环
                    break retry;
                // 获取线程池控制状态
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs) // 此次的状态与上次获取的状态不相同
                    // 跳过剩余部分,继续循环
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        // worker开始标识
        boolean workerStarted = false;
        // worker被添加标识
        boolean workerAdded = false;
        // 
        Worker w = null;
        try {
            // 初始化worker
            w = new Worker(firstTask);
            // 获取worker对应的线程
            final Thread t = w.thread;
            if (t != null) { // 线程不为null
                // 线程池锁
                final ReentrantLock mainLock = this.mainLock;
                // 获取锁
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    // 线程池的运行状态
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||                                    // 小于SHUTDOWN
                        (rs == SHUTDOWN && firstTask == null)) {            // 等于SHUTDOWN并且firstTask为null
                        if (t.isAlive()) // precheck that t is startable    // 线程刚添加进来,还未启动就存活
                            // 抛出线程状态异常
                            throw new IllegalThreadStateException();
                        // 将worker添加到worker集合
                        workers.add(w);
                        // 获取worker集合的大小
                        int s = workers.size();
                        if (s > largestPoolSize) // 队列大小大于largestPoolSize
                            // 重新设置largestPoolSize
                            largestPoolSize = s;
                        // 设置worker已被添加标识
                        workerAdded = true;
                    }
                } finally {
                    // 释放锁
                    mainLock.unlock();
                }
                if (workerAdded) { // worker被添加
                    // 开始执行worker的run方法
                    t.start();
                    // 设置worker已开始标识
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted) // worker没有开始
                // 添加worker失败
                addWorkerFailed(w);
        }
        return workerStarted;
    }

3.3 worker.run()

工作线程:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。我们可以从Worker类的run()方法里看到这点。

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

说明:此函数中会实际执行给定任务(即调用用户重写的run方法),并且当给定任务完成后,会继续从阻塞队列中取任务,直到阻塞队列为空(即任务全部完成)。在执行给定任务时,会调用钩子函数,利用钩子函数可以完成用户自定义的一些逻辑。在runWorker中会调用到getTask函数和processWorkerExit钩子函数,其中,getTask函数源码如下

3.4 getTask()

说明:此函数用于从workerQueue阻塞队列中获取Runnable对象,由于是阻塞队列,所以支持有限时间等待(poll)和无限时间等待(take)。在该函数中还会响应shutDown和、shutDownNow函数的操作,若检测到线程池处于SHUTDOWN或STOP状态,则会返回null,而不再返回阻塞队列中的Runnalbe对象。

processWorkerExit函数是在worker退出时调用到的钩子函数,而引起worker退出的主要因素如下

① 阻塞队列已经为空,即没有任务可以运行了。

② 调用了shutDown或shutDownNow函数

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) { // 无限循环,确保操作成功
            // 获取线程池控制状态
            int c = ctl.get();
            // 运行的状态
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { // 大于等于SHUTDOWN(表示调用了shutDown)并且(大于等于STOP(调用了shutDownNow)或者worker阻塞队列为空)
                // 减少worker的数量
                decrementWorkerCount();
                // 返回null,不执行任务
                return null;
            }
            // 获取worker数量
            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 是否允许coreThread超时或者workerCount大于核心大小

            if ((wc > maximumPoolSize || (timed && timedOut))     // worker数量大于maximumPoolSize
                && (wc > 1 || workQueue.isEmpty())) {            // workerCount大于1或者worker阻塞队列为空(在阻塞队列不为空时,需要保证至少有一个wc)
                if (compareAndDecrementWorkerCount(c))            // 比较并减少workerCount
                    // 返回null,不执行任务,该worker会退出
                    return null;
                // 跳过剩余部分,继续循环
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :    // 等待指定时间
                    workQueue.take();                                        // 一直等待,直到有元素
                if (r != null)
                    return r;
                // 等待指定时间后,没有获取元素,则超时
                timedOut = true;
            } catch (InterruptedException retry) {
                // 抛出了被中断异常,重试,没有超时
                timedOut = false;
            }
        }
    }

3.5 待定

4. JDK 线程池的使用

4.1 参数介绍

介绍一下几个参数就行:

4.2合理地配置线程池

要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
❑ 任务的性质:CPU密集型任务、IO密集型任务和混合型任务。

❑ 任务的优先级:高、中和低。

❑ 任务的执行时间:长、中和短。

❑ 任务的依赖性:是否依赖其他系统资源,如数据库连接。

CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。

混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。

可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

4.3 线程池的监控

如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性。
❑ taskCount:线程池需要执行的任务数量。

❑ completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。

❑ largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。

❑ getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。❑ getActiveCount:获取活动的线程数。

建议使用有界队列。

发布了37 篇原创文章 · 获赞 6 · 访问量 4634

猜你喜欢

转载自blog.csdn.net/littlewhitevg/article/details/105587016