【并发编程学习篇】并发线程池底层原理详解与源码分析

在这里插入图片描述

一、线程池与线程对比

package bat.ke.qq.com.threadpool;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/***
 * 使用线程的方式去执行程序
 */
public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Long start = System.currentTimeMillis();
        final Random random = new Random();
        final List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 100000; i++) {
            Thread thread = new Thread() {
                @Override
                public void run() {
                    list.add(random.nextInt());
                }
            };
            thread.start();
            thread.join();  // 等待调用join方法的线程结束之后,程序再继续执行
        }
        System.out.println("时间:" + (System.currentTimeMillis() - start));
        System.out.println("大小:" + list.size());
}

执行结果:
时间:6422
大小:100000

线程池

package bat.ke.qq.com.threadpool;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/***
 * 线程池执行
 */
public class ThreadPoolTest {

    public static void main(String[] args) throws InterruptedException {
        Long start = System.currentTimeMillis();
        final Random random = new Random();
        final List<Integer> list = new ArrayList<Integer>();
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 100000; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    list.add(random.nextInt());
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.DAYS);
        System.out.println("时间:"+(System.currentTimeMillis() - start));
        System.out.println("大小:"+list.size());
}

执行结果:
时间:33
大小:100000

二、为什么要用线程池?

线程池的优点:

  1. 降低资源消耗:通过 线程复用 来降低线程创建和销毁造成的消耗,创建线程涉及到了用户态到内核态的切换,非常影响性能;
  2. 提高响应速度:当任务到达时,任务可以不需要的等待,而直接被线程池中的空闲线程执行;
  3. 提高线程的可管理性:线程是稀缺资源,通过线程池可以进行统一的分配,调优和监控。

线程池的缺点:

  1. 增加了系统的复杂性:线程池的配置项很多,如果不恰当,可能会造成性能问题;
  2. 每一个任务的执行时间变长:线程池的任务是共享线程,不同任务之间互相影响,当线程池中线程数量很多时,每个任务执行时间可能会变长。

三、为什么不推荐使用Executors默认创建的线程池?

3.1 Executors.newFixedThreadPool(10)

在这里插入图片描述

LinkedBlockingQueue队列的容量默认为Integer.MAX_VALUE,大批量的任务提交到队列中 阻塞等待 时,可能导致 OOM

3.2 Executors.newCachedThreadPool()

在这里插入图片描述

非核心线程设置为Integer.MAX_VALUE,且 SynchronousQueue 同步队列,生产者消费者的模型,只要有一个任务被提交到队列中,就会 马上创建一个线程执行任务 ,这样可能创建非常多的非核心线程,所以可能导致 CPU100%

3.3 Executors.newSingleThreadExecutor()

在这里插入图片描述

与newFixedThreadPool()一样使用了 LinkedBlockingQueue 队列的容量默认为Integer.MAX_VALUE,大批量的任务提交到队列中阻塞等待时,可能导致 OOM

四、自定义线程池原理分析

image.png

4.1 自定义线程池使用方式:

public static void main(String[] args) {
    // 线程池的核心线程数如何设置
    // 任务可以分为两种:CPU密集,IO密集。
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
            1,
            2,
            1L,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    // ...
                    return t;
                }
            },
            new ThreadPoolExecutor.AbortPolicy()
    );

    executor.execute(任务);
    executor.submit(有返回结果的任务);
}

4.2 线程池属性认知

// AtomicInteger,就是一个int,写操作用CAS实现,保证了原子性
// ctl维护这线程池的2个核心内容:
// 1:线程池状态(高3位,维护着线程池状态)
// 2:工作线程数量(核心线程+非核心线程,低29位,维护着工作线程个数)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// COUNT_BITS= 29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 工作线程的最大个数
// 00100000 00000000 00000000 00000000(左移29位) - 1
// 00011111 11111111 11111111 11111111  
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;


private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// 拿到线程池状态  ~CAPACITY 000111111111111111111111111111111  取反 111xxx....
// 011... 
// 111...
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 拿到工作线程个数
// ...0000000111111
// ...1111111111111
private static int workerCountOf(int c)  { return c & CAPACITY; }

4.3 线程池状态

image.png
execute方法原理

在这里插入图片描述

4.4 execute方法源码解析

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1、判断当前的线程数是否小于corePoolSiz,
         * 如果是,使用入参任务通过addWord方法创建一个新的线程
         * 如果能完成新线程创建exexute方法结束,成功提交任务;
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        /**
        * 2、如果状态为运行并且能成功加入任务到工作队列后,再进行一次check
        * 如果状态在任务加入队列后变为了非运行(有可能是在执行到这里线程池shutdown了),将这个任务从队列中移除,执行指定的拒绝策略(reject);
        */
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
           	// 线程池状态是RUNNING,判断工作线程数是否是0个。
          	// 将核心线程设置为0的情况,所有工作线程都是非核心线程,此时需要。创建一个任务去执行队列里的任务            // 核心线程也可以通过keepAlived超时被销毁,所以如果恰巧核心线程被销毁,也会出现当前效果
            else if (workerCountOf(recheck) == 0)
            
                addWorker(null, false);
        }
        /**
        * 3、可能工作队列中的任务存满了,没添加进去,到这就要添加非核心线程去处理任务
        * 如果失败,则是线程池已经shutdown或者线程池已经达到饱和状态,执行reject;
        */
        else if (!addWorker(command, false))
            reject(command);
    }

4.5 addWorker方法源码解析

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        // 判断线程池状态
        if (rs >= SHUTDOWN &&
              // 判断如果线程池的状态为SHUTDOWN,还要处理工作队列中的任务
              // 如果你添加工作线程的方式,是任务的非核心线程,并且工作队列还有任务
            ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
            return false;

        // 判断工作线程个数
        for (;;) {
            // 阿巴阿巴……
            int wc = workerCountOf(c);
            // 判断1:工作线程是否已经 == 工作线程最大个数
            // 判断2-true判断:判断是核心线程么?如果是判断是否超过核心线程个数
            // 判断2-false判断:如果是非核心线程,查看是否超过设置的最大线程数
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 对工作线程进行 + 1操作
            if (compareAndIncrementWorkerCount(c))
                // +1成功,跳出外层循环,执行添加工作线程的业务
                // 以CAS方式,对ctl+1,多线程并发操作,只有会有一个成功
                break xxx;
            // 重新拿ctl,
            c = ctl.get();
            // 判断线程池状态是否有变化
            if (runStateOf(c) != rs)
                continue xxx;
        }
    }

    // 添加工作线程的业务  
    // 工作线程启动了吗?
    boolean workerStarted = false;
	// 工作线程添加了吗?
    boolean workerAdded = false;
    // Worker就是工作线程
    Worker w = null;
    try {
        // 创建工作线程,将任务传到Worker中
        w = new Worker(firstTask);
        final Thread t = w.thread;
        // 只有你写的线程工厂返回的是null,这里才会为null
        if (t != null) {
            // 获取锁资源
            final ReentrantLock mainLock = this.mainLock;
            // 加锁。  因为我要在启动这个工作线程时,避免线程池状态发生变化,加锁。
            mainLock.lock();
            try {
                // 重新获取ctl,拿到线程池状态
                int rs = runStateOf(ctl.get());
                // DCL i think you know~~~
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                   	// 判断Worker中的thread是否已经启动了,一般不会启动,除非你在线程工厂把他启动了
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    // 将工作线程存储到hashSet中
                    workers.add(w);
                    // 获取工作线程个数,判断是否需要修改最大工作线程数记录。
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    // 工作线程添加成功     0
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 如果添加成功
            if (workerAdded) {
                // 启动工作线程
                t.start();
                // 设置标识为true
                workerStarted = true;
            }
        }
    } finally {
        // 如果工作线程启动失败
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

// 如果添加工作线程失败,执行
private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 说明worker可能存放到了workers的hashSet中。
        if (w != null)
            // 移除!
            workers.remove(w);
        // 减掉workerCount的数值 -1
        decrementWorkerCount();
        // 尝试干掉自己
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

4.6 runwoker方法

final void runWorker(Worker w) {
    // 拿到当前线程对象
    Thread wt = Thread.currentThread();
    // 拿到worker中存放的Runnable
    Runnable task = w.firstTask;
    // 将worker中的任务清空
    w.firstTask = null;
    // 揍是一个标识
    boolean completedAbruptly = true;
    try {
        // 如果Worker自身携带任务,直接执行
        // 如果Worker携带的是null,通过getTask去工作队列获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // 判断线程池状态是否大于等于STOP,如果是要中断当前线程
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 // 中断当前线程(DCL)
                 (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);
    }
}

4.7 getTask工作线程排队拿任务

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

    for (;;) {
        // 阿巴
        int c = ctl.get();
        int rs = runStateOf(c);

        // 线程池状态判断
        // 如果线程池状态为SHUTDOWN && 工作队列为空
        // 如果线程池状态为STOP
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // 对工作线程个数--
            decrementWorkerCount();
            return null;
        }

        // 对数量的判断。
        int wc = workerCountOf(c);

        // 判断核心线程是否允许超时?
        // 工作线程个数是否大于核心线程数
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 判断工作线程是否超过了最大线程数 && 工作队列为null
        if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
            // 工作线程数有问题,必须-1,干掉当前工作线程
            // 工作线程是否超过了核心线程,如果超时,就干掉当前线程
            // 对工作线程个数--
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 如果是非核心,走poll,拉取工作队列任务,
            // 如果是核心线程,走take一直阻塞,拉取工作队列任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            	// 当工作队列没有任务时,这时就会被Condition通过await阻塞线程
            	// 当有任务添加到工作线程后,这是添加完任务后,就会用过Condition.signal唤醒阻塞的线程
                workQueue.take();
            if (r != null)
                return r;
            // 执行的poll方法,并且在指定时间没拿到任务,
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

4.8 processWorkerExit工作线程告辞~

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果是不正常操作,需要先对工作线程数-- (如果正常情况,getTask就--了)
    if (completedAbruptly) 
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 将当前工作线程完整的任务个数赋值给整个线程池中的任务数
        completedTaskCount += w.completedTasks;
        // 干掉当前工作线程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    // 线程池是否可以中止,线程池状态是否发生变化。
    tryTerminate();

  
    int c = ctl.get();
    //如果当前线程池状态小于STOP
    if (runStateLessThan(c, STOP)) {
        // 判断线程池中的工作队列是否还有任务,并且工作线程是否还在。
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 添加非核心空任务的线程处理工作队列中的任务
        addWorker(null, false);
    }
}

五、4个拒绝策略

CallerRunsPolicy:由调用execute方法提交任务的线程来执行这个任务;

AbortPolicy:抛出异常RejectedExecutionException拒绝提交任务;

DiscardPolicy:直接抛弃任务,不做任何处理;

DiscardOldestPolicy:去除任务队列中的第一个任务(最旧的),重新提交;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java学习者柯十一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值