高并发高级篇(四)--线程池工作流程篇

这一篇,我想就线程池的使用从全流程的角度来说下,线程池是如何工作的。
在这里插入图片描述
在这里插入图片描述

我们以一个调用示例,来逐步深入。

一、固定大小线程池调用示例

1、我们先创建一个corePoolSize大小为5的线程池,然后提交10个任务。
public class ThreadPoolDemo {

public static class MyTask implements Runnable{  

    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
        System.out.println(System.currentTimeMillis()+"--Thread Id:"+Thread.currentThread().getId());  
        try {  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
      
}  
  
public static void main(String[] args) {  
    MyTask task = new MyTask();  
    //定义corePoolSize和maxiumPoolSize大小为5的线程池  
    ExecutorService es = Executors.newFixedThreadPool(5);  
    for (int i = 0; i < 10; i++) {  
        <strong>es.execute(task);</strong>  
    }  
}  

}
首先,我们要知道的是,在示例中,我们初始化的线程池内容是什么样子的。我们使用的是大小固定的线程池Executors.newFixedThreadPool(5)。

public static ExecutorService newFixedThreadPool(int nThreads) {  
        return new ThreadPoolExecutor(nThreads, nThreads,  
                                      0L, TimeUnit.MILLISECONDS,  
                                      new LinkedBlockingQueue<Runnable>());  
    }  
  
public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue) {  
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
             Executors.defaultThreadFactory(), defaultHandler);  
    }  
  
public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler) {  
        if (corePoolSize < 0 ||  
            maximumPoolSize <= 0 ||  
            maximumPoolSize < corePoolSize ||  
            keepAliveTime < 0)  
            throw new IllegalArgumentException();  
        if (workQueue == null || threadFactory == null || handler == null)  
            throw new NullPointerException();  
        this.corePoolSize = corePoolSize;  
        this.maximumPoolSize = maximumPoolSize;  
        this.workQueue = workQueue;  
        this.keepAliveTime = unit.toNanos(keepAliveTime);  
        this.threadFactory = threadFactory;  
        this.handler = handler;  
    }  

初始化完成,各属性值见下图,清晰明白,其他不多说。

注意两个属性(要不然看源码都不知道找谁):threadFactory为Executors中的静态内部类DefaultThreadFactory;handler(拒绝策略处理器)使用的是默认的ThreadPoolExecutor中的静态内部类AbortPolicy。
在这里插入图片描述
2、我们从es.execute(task)开始分析整个的执行流程。execute方法是ThreadPoolExecutor中的方法,源码内容如下:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
①初始时,poolSize值为0,条件poolSize >= corePoolSize不成立,走的都是方法addIfUnderCorePoolSize(command),其中command是提交的需要执行的任务。

该方法源码如下:

/** 
     * Creates and starts a new thread running firstTask as its first 
     * task, only if fewer than corePoolSize threads are running 
     * and the pool is not shut down. 
     * @param firstTask the task the new thread should run first (or 
     * null if none) 
     * @return true if successful 
     */  
    private boolean addIfUnderCorePoolSize(Runnable firstTask) {  
        Thread t = null;  
        final ReentrantLock mainLock = this.mainLock;  
        mainLock.lock();  
        try {  
            if (poolSize < corePoolSize && runState == RUNNING)  
                t = addThread(firstTask);  
        } finally {  
            mainLock.unlock();  
        }  
        if (t == null)  
            return false;  
        t.start();  
        return true;  
    }  

核心代码中关键方法为:addThread(firstTask),源码如下:

/** 
     * Creates and returns a new thread running firstTask as its first 
     * task. Call only while holding mainLock. 
     * 
     * @param firstTask the task the new thread should run first (or 
     * null if none) 
     * @return the new thread, or null if threadFactory fails to create thread 
     */  
    private Thread addThread(Runnable firstTask) {  
        Worker w = new Worker(firstTask);  
        Thread t = threadFactory.newThread(w);  
        if (t != null) {  
            w.thread = t;  
            workers.add(w);  
            int nt = ++poolSize;  
            if (nt > largestPoolSize)  
                largestPoolSize = nt;  
        }  
        return t;  
    }  

它的作用是在线程池中的线程数量(poolsize)还没有达到指定的corePoolSize之前,将任务提交给线程池中新创建的线程处理(Worker w = new Worker(firstTask);Thread t = threadFactory.newThread(w);),这里做了一层封装,把任务包装成Worker,然后使用Work对象在threadFactory创建新的线程,也就是使用这个线程去处理work。由于这个过程都能创建新线程处理任务,所以if (t == null)不成立,返回的是true,所以条件 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))不成立,下面的逻辑不会执行(这也是设计巧妙的地方)。其他逻辑很简单(每创建一个新线程,poolsize都会自增1,统计目前线程池的大小)。

②当线程池大小达到corePoolSize时,也就是条件poolSize >= corePoolSize成立,不会再执行addIfUnderCorePoolSize方法,因为只要线程池不关闭,接收新任务和处理等待队列的任务过程就不会停,所以runState在这个过程始终是0,也就是RUNNING状态(RUNNING-0: Accept new tasks and process queued tasks)。

if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {  
            if (runState == RUNNING && workQueue.offer(command)) {  
                if (runState != RUNNING || poolSize == 0)  
                    ensureQueuedTaskHandled(command);  
            }  
            else if (!addIfUnderMaximumPoolSize(command))  
                reject(command); // is shutdown or saturated  
        }  

左半条件成立后,那么接着就要进行 workQueue.offer(command)了。等待任务队列(workQueue)这回就要派上用场了。因为固定大小的线程池使用的等待任务队列是LinkedBlockingQueue,所以我们看下它的offer方法,核心代码就一行-----insert(e),只要插入成功,就返回true(c变为0,c>=0肯定是成立的)。其实跟我们前面介绍队列术时候的put方法一样,就是放任务,前面帖子已经说过,这里不再赘述,不清楚的可以回过去看。

public boolean offer(E e) {  
        if (e == null) throw new NullPointerException();  
        final AtomicInteger count = this.count;  
        if (count.get() == capacity)  
            return false;  
        int c = -1;  
        final ReentrantLock putLock = this.putLock;  
        putLock.lock();  
        try {  
            if (count.get() < capacity) {  
                insert(e);  
                c = count.getAndIncrement();  
                if (c + 1 < capacity)  
                    notFull.signal();  
            }  
        } finally {  
            putLock.unlock();  
        }  
        if (c == 0)  
            signalNotEmpty();  
        return c >= 0;  
    }  

我们使用固定大小线程池,涉及到核心方法只有addIfUnderCorePoolSize,因为固定线程池使用的等待任务队列是LinkedBlockingQueue,接近于无限队列,因此在没有达到Integer.MAX_VALUE个任务时,是不会执行到addIfUnderMaximumPoolSize方法的。为了看下addIfUnderMaximumPoolSize做了什么,我们还需要举一例,使用newCachedThreadPool。

二、newCachedThreadPool使用举例

为什么要用它作为举例,因为它有个特殊之处,workQueue是使用SynchronousQueue,无大小,也就是人们说的直接提交队列(不明白的回头看我写的队列术解密),因此很容易走到方法addIfUnderMaximumPoolSize(if under maximun pool size,add command)。

我们看下它的源码:

private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {  
        Thread t = null;  
        final ReentrantLock mainLock = this.mainLock;  
        mainLock.lock();  
        try {  
            if (poolSize < maximumPoolSize && runState == RUNNING)  
                t = addThread(firstTask);  
        } finally {  
            mainLock.unlock();  
        }  
        if (t == null)  
            return false;  
        t.start();  
        return true;  
    }  

这段源码,核心代码也就一段:
if (poolSize < maximumPoolSize && runState == RUNNING)
t = addThread(firstTask);
当等待任务队列已满时,那么就需要判断线程池中处理任务的线程个数是否达到了规定的最大线程池大小maxinumPoolSize,如果没有达到,则将任务使用work对象包裹任务,然后利用work对象创建新线程至线程池中,处理该任务;如果线程池大小达到了maxinumPoolSize,那么就需要执行reject(command)–执行拒绝策略了。
附示例及示例程序执行过程图:

public static void main(String[] args) {  
        MyTask task = new MyTask();  
        //定义corePoolSize和maxiumPoolSize大小为5的线程池  
        ExecutorService es = Executors.newCachedThreadPool();  
        for (int i = 0; i < 10; i++) {  
            es.execute(task);  
        }  
    }  

执行过程程序图:
在这里插入图片描述
在这里插入图片描述

四种线程池
常见四种线程池
如果你不想自己写一个线程池,那么你可以从下面看看有没有符合你要求的(一般都够用了),如果有,那么很好你直接用就行了,如果没有,那你就老老实实自己去写一个吧

Java通过Executors提供了四种线程池,这四种线程池都是直接或间接配置ThreadPoolExecutor的参数实现的,下面我都会贴出这四种线程池构造函数的源码,各位大佬们一看便知!

来,走起:

CachedThreadPool()
可缓存线程池:

线程数无限制

有空闲线程则复用空闲线程,若无空闲线程则新建线程

一定程序减少频繁创建/销毁线程,减少系统开销

创建方法:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

源码:

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
通过我上面行云流水谈笑风生天马行空滔滔不绝的对各种参数的说明,这个源码你肯定一眼就看懂了,想都不用想(下面三种一样啦)

FixedThreadPool()
定长线程池:

可控制线程最大并发数(同时执行的线程数)

超出的线程会在队列中等待

创建方法:

//nThreads => 最大线程数即maximumPoolSize
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);

//threadFactory => 创建线程的方法,这就是我叫你别理他的那个星期六!你还看!
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
2个参数的构造方法源码,不用我贴你也知道他把星期六放在了哪个位置!所以我就不贴了,省下篇幅给我扯皮

ScheduledThreadPool()
定长线程池:

支持定时及周期性任务执行。
ScheduledThreadPoolExecutor继续于ThreadPoolExecutor
参考:https://www.jianshu.com/p/925dba9f5969
创建方法:

//nThreads => 最大线程数即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
源码:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}

//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
SingleThreadExecutor()
单线程化的线程池:

有且仅有一个工作线程执行任务

所有任务按照指定顺序执行,即遵循队列的入队出队规则

创建方法:

ExecutorService singleThreadPool = Executors.newSingleThreadPool();

源码:

public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
还有一个Executors.newSingleThreadScheduledExecutor()结合了3和4,就不介绍了,基本不用。
Executors是一个单独的类,提供了四种创建java内置的线程池的方法
参考:https://www.jianshu.com/p/50fffbf21b39
https://cuisuqiang.iteye.com/blog/2019372
http://blog.51cto.com/13579086/2087880

线程池的实现类有两个(实现ExecutorService ):
ScheduledThreadPoolExecutor
ThreadPoolExecutor

submit()和execute()的区别
JDK5往后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,它们的区别是:

execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口
submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口
参考:https://www.jianshu.com/p/29610984f1dd
https://blog.csdn.net/peachpi/article/details/6771946

继续学习:
https://www.cnblogs.com/dolphin0520/p/3932921.html
线程池的5种状态:http://www.cnblogs.com/-wyl/p/9760670.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值