深入浅出线程池——从源码层面解析线程池

深入浅出线程池

2024-09-29
13k

前言

鄙人在公司实习时,看见了公司的自定义线程池结合CompletableFuture异步获取结果,对线程池的实现感兴趣,于是花了些时间来解决来弄清楚线程池的执行原理
本文将会探讨
Executor、Executors、ExecutorService、AbstractExecutorService、ThreadPoolExecutor、ForkJoinPool之间的关系
先简单介绍一下之间的关系,Executor是最底层的定义,ExecutorService继承了Executor

AbstractExecutorService是ExecutorService的默认实现,而ThreadPoolExecutor和ForkPool都是AbstrackExecutor的子类

Executor ——> 一切的根源

Executor接口是JUC提供的一个接口,通常用于提交Runnable任务,用于显示替代创建线程。

ExecutorService ——> 线程池管理接口

ExecutorService 是 Java 中用于管理和控制线程池的接口。它提供了管理线程生命周期的方法,并允许提交任务以供执行。换句话说,Executor中只定义了一个execute方法,管理线程池的方法全都是在这个接口中定义的

使用示例


    ExecutorService executor = Executors.newFixedThreadPool(10);  
  
    
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {  
        
        try {  
            Thread.sleep(2000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }        return "Hello, World!";  
    }, executor);  
  
    
    future.thenAccept(result -> {  
        System.out.println("Result: " + result);  
    });  
    
    executor.shutdown();  
}

本文仅仅探究线程池的使用,关于CompletableFuture的部分,鄙人还有另外一篇源码解析的文章,欢迎观看!
这里简单提一下,CompletableFuture实际上是使用我们传进去的线程池参数来执行任务的,会调用线程池Executor中定义的execute方法,而这个地方这个方法的实现者是ThreadPoolExecutor,后文会详细分析,这里先简单介绍。

源码解析

public interface ExecutorService extends Executor {

    
    void shutdown();

    
    List<Runnable> shutdownNow();

    
    boolean isShutdown();

    
    boolean isTerminated();

    
    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

    
    <T> Future<T> submit(Callable<T> task);

    
    <T> Future<T> submit(Runnable task, T result);

    
    Future<?> submit(Runnable task);

    
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

    
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

    
    <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

    
    <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

可以发现,ExecutorService定义了一系列线程池管理方法

AbstractExecutorService ——> ExecutorService 接口的默认实现

源码


public abstract class AbstractExecutorService implements ExecutorService {

    
    public AbstractExecutorService() {}

    
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

    
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

    
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                              boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
        
    }

    
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
        
    }

    
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        
    }

    
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
        
    }

    
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
        
    }

    
    private static <T> void cancelAll(ArrayList<Future<T>> futures) {
        
    }

    
    private static <T> void cancelAll(ArrayList<Future<T>> futures, int j) {
        
    }
}

源码中实现并定义了一些线程池的管理和操作,有一个地方是比较有意思的

AbstractExecutorService并没有实现execute方法,从而这个地方的调用实际上是调用的AbstractExecutorService的子类实现的方法,这中设计方法叫做模板方法模式

模板方法模式

由父类定义一个算法的骨架,将一些步骤实现延迟到子类中,子类可以重写这些步骤来实特定的行为,但是算法的整体逻辑由父类来设计

回到上面的示例代码中

ExecutorService executor = Executors.newFixedThreadPool(10);  
  

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {  
    
    try {  
        Thread.sleep(2000);  
    } catch (InterruptedException e) {  
        e.printStackTrace();  
    }        return "Hello, World!";  
}, executor);

当我们进入到Executors中,我们就能找到真相

原来如此,execute实际调用的是ThreadPoolExecutor中的实现!

ThreadPoolExecutor ——> 灵活的方式来创建和管理线程池

使用示例

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize, 
    maximumPoolSize, 
    keepAliveTime, 
    TimeUnit.MILLISECONDS, 
    new LinkedBlockingQueue<Runnable>(), 
    new ThreadPoolExecutor.AbortPolicy() 
);
executor.execute(task); 
executor.shutdown(); 

核心参数

参数意义
corePoolSize线程池中的核心线程数
workQueue存放提交的task
maximumPoolSize线程池中允许的最大线程数
threadFactory线程工厂, 用来创建线程, 由Executors#defaultThreadFactory实现
keepAliveTime空闲线程存活时间(默认是临时线程, 也可设置为核心线程)
unit空闲线程存活时间单位枚举

关于线程创建后任务会如何执行

  1. 线程数小于核心线程数时,直接创建线程来执行任务
  2. 大于核心线程且工作队列没满就将任务提交给工作队列
  3. 大于核心线程数且队列已满,直接创建一个临时线程来处理任务
  4. 大于最大线程数,工作队列已满,执行拒绝策略

源码解析

当我们进入到这个类中,会发现有好多代码,这里就不贴出来了,我们只挑重点的来解析,我们来顺着上面的示例代码来深入

定义和属性

public class ThreadPoolExecutor extends AbstractExecutorService {
	
	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
	
	
	private static final int COUNT_BITS = Integer.SIZE - 3

	
	private static final int COUNT_MASK = (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;
		

	
	private final BlockingQueue<Runnable> workQueue;
	
	
	private final ReentrantLock mainLock = new ReentrantLock();
	
	
	private final HashSet<Worker> workers = new HashSet<>();
	
	
	private final Condition termination = mainLock.newCondition();
	
	
	private int largestPoolSize;
	
	
	private long completedTaskCount;
	
	
	
	
	private volatile ThreadFactory threadFactory;
	
	
	private volatile RejectedExecutionHandler handler;
	
	
	private volatile long keepAliveTime;
	
	
	
	private volatile boolean allowCoreThreadTimeOut;
	
	
	private volatile int corePoolSize;
	
	
	private volatile int maximumPoolSize;
}

我们点进去new ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,  
                          int maximumPoolSize,  
                          long keepAliveTime,  
                          TimeUnit unit,  
                          BlockingQueue<Runnable> workQueue,  
                          RejectedExecutionHandler handler) {  
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
         Executors.defaultThreadFactory(), handler);  
}

从这里我们可以看见有一个参数是Executors中的工厂方法,这里我们稍后再提
顺着this去翻找

发现这里仅仅进行了一些赋值,emm,线索好像断了。没关系,还记得我们上文提到的execute方法吗?ThreadPoolExecutor中需要实现这个方法,让我们去看看吧

public void execute(Runnable command) {
    
    if (command == null)
        throw new NullPointerException();
    
    
    int c = ctl.get();
    
    
    if (workerCountOf(c) < corePoolSize) {
        
        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);
        
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    } 
    
    else if (!addWorker(command, false))
        
        reject(command);
}

这里就是我们上文提到的提交任务时会遇到的情况:

  1. 运行中的线程数 < 核心线程数,就尝试添加一个新的工作线程来执行任务
  2. 大于核心线程数,就尝试将任务加到工作队列中
  3. 如果无法加入队列就尝试添加一个新的工作线程
  4. 再失败就拒绝任务
    接着来看addWorker
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (int c = ctl.get();;) {
            
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP)
                    || firstTask != null
                    || workQueue.isEmpty()))
                return false;
    
            for (;;) {
                
                if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                    return false;
                
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  
                if (runStateAtLeast(c, SHUTDOWN))
                    continue retry;
                
            }
        }
    
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask); 
            final Thread t = w.thread; 
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock(); 
                try {
                    
                    int c = ctl.get();
    
                    
                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        if (t.getState() != Thread.State.NEW)
                            throw new IllegalThreadStateException();
                        workers.add(w); 
                        workerAdded = true;
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s; 
                    }
                } finally {
                    mainLock.unlock(); 
                }
                if (workerAdded) {
                    t.start(); 
                    workerStarted = true;
                }
            }
        } finally {
            if (!workerStarted)
                addWorkerFailed(w); 
        }
        return workerStarted;
    }
    我们到这里就很清楚了,所谓的线程池不过是管理的一堆线程的工具,实际上就是帮助我们判断合适去新增线程何时复用线程。
    最后让我们去看看线程复用是如何实现的!

线程复用的奥秘

ThreadPoolExecutor中有一个静态内部类Worker

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    private static final long serialVersionUID = 6138294804551838833L;

    
    @SuppressWarnings("serial") 
    final Thread thread;

    
    @SuppressWarnings("serial") 
    Runnable firstTask;

    
    volatile long completedTasks;

    
    Worker(Runnable firstTask) {
        setState(-1); 
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

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

    
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    
    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    
    public void lock() {
        acquire(1);
    }

    
    public boolean tryLock() {
        return tryAcquire(1);
    }

    
    public void unlock() {
        release(1);
    }

    
    public boolean isLocked() {
        return isHeldExclusively();
    }

    
    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

Worker是定义了一个线程和要执行的初始任务,而线程复用要关注到这行
 

跳转之后,发现是调用的ThreadPoolExecutor中定义的runWorker方法

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); 
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) {
            w.lock();
            
            
            
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                try {
                    task.run();
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

发现这里的逻辑:

  1. 获取当前线程、获取Worker中的初始任务并清空,解锁Worker允许终端
  2. 只要任务不为null或者从getTask中获取到新任务就一直循环执行,从而达到线程复用
  3. 检测线程池状态,进行一些钩子方法
    到这里我们才真正找到线程池的奥秘,原来是通过循环来不断从任务队列中获取任务执行,而不是执行完任务之后就终止线程!!!

ForkJoinPool ——> 递归拆分!让并行嗨起来!并行流的原理!

篇幅有限简单介绍一下

主要组件

  1. ForkJoinTask:任务的基本单位,提供fork、join,用于任务的分解和合并
  2. ForkJoinWorkerThread:执行ForkJoinTask的工作线程
  3. WorkQueue:工作线程用于存储任务
  4. ForkJoinPool:管理和调度ForkJoinTask的执行

核心原理

工作窃取法:

  • 工作窃取:当一个工作线程完成了自己的任务队列中的任务后,它会尝试从其他线程的队列中窃取任务来执行。
  • 双端队列:每个工作线程的任务队列是一个双端队列,线程从队列的一端(通常是头部)获取任务执行,而其他线程从队列的另一端(通常是尾部)窃取任务。

并行流

  • 使用stream()方法将数据源转换为顺序流,或使用parallelStream()方法将数据源转换为并行流。
  • 使用中间操作(如filter、map、sorted等)和终端操作(如forEach、collect、reduce等)定义流的处理逻辑。
  • 执行并行处理:
  • 并行流会自动使用ForkJoinPool来并行处理数据。每个操作会被拆分成多个子任务,并由ForkJoinPool中的工作线程并行执行。

常见八股

为什么使用抽象类来实现接口

  1. 因为普通类去实现接口中的方法,子类在继承的时候必须重写父类中的方法不利于代码的复用
  2. 抽象类可以提供一些默认实现,从而使得子类直接使用即可
  3. 可以强制子类去实现某个方法(abstract)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值