线程池核心原理

123 阅读10分钟

1,示例:

ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i <= 5 ; i++) {
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("这里是任务1");
        }
    });
}

ThreadPoolExecutor类中下面属性:
private final BlockingQueue<Runnable> workQueue;
private final HashSet<Worker> workers = new HashSet<Worker>(); 
//Worker里的属性Thread thread 是真正执行任务的线程.
//Worker里的属性Runnable firstTask 是真正执行的任务.

image.png

2,分析ThreadPoolExecutor类中的execute方法

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
            
        int c = ctl.get();
        
        //1.当前池中线程比核心数少,新建一个线程执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        
        //2.核心池已满,但任务队列 未满,添加到队列中
        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);
        }
        //3.核心池已满,队列已满,试着创建一个新 线程
        else if (!addWorker(command, false)) 
            reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了, 拒绝任务
            
    }

继续分析addWorker方法

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:  //goto语句,避免死循环
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        如果线程处于非运行状态,并且 rs 不等于 SHUTDOWN 且 firstTask 不等于空且且 workQueue 为空,直接返回 false(表示不可添加 work 状态)
        1. 线程池已经 shutdown 后,还要添加新的任务,拒绝
        2. (第二个判断)SHUTDOWN 状态不接受新任务,但仍然会执行已经加入任务队列的任 务,所以当进入 SHUTDOWN 状态,而传进来的任务为空,
            并且任务队列不为空的时候,是允许添加 新线程的,如果把这个条件取反,就表示不允许添加 worker
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) { //自旋
            int wc = workerCountOf(c); //获得Worker工作线程数
            //如果工作线程数大于默认容量大小或者大于核心线程数大小,则直接返回 false 表示不 能再添加 worker。
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c)) //通过cas来增加工作线程数, 如果 cas 失败,则直接重试
                break retry;
            c = ctl.get();  // Re-read ctl  //再次获取ctl的值 
            if (runStateOf(c) != rs) //这里如果不想等,说明线程的状态发生了变化,继续重试
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    
    //上面这段代码主要是对 worker 数量做原子+1 操作,下面的逻辑才是正式构建一个 worker
    boolean workerStarted = false; //工作线程是否启动的标识
    boolean workerAdded = false; //工作线程是否已经添加成功的标识
    Worker w = null;
    try {
        w = new Worker(firstTask); //构建一个Worker,这个worker是什么呢?我们 可以看到构造方法里面传入了一个 Runnable 对象
        final Thread t = w.thread; //从 worker 对象中取出线程
        if (t != 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());
                //只有当前线程池是正在运行状态,[或是 SHUTDOWN 且 firstTask 为空],才
能添加到 workers 集合中
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    //任务刚封装到 work 里面,还没 start,你封装的线程就是alive,几个意思?肯定是要抛异常出去的
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w); //将新创建的Worker添加到workers集合中
                    int s = workers.size();
                    //如果集合中的工作线程数大于最大线程数,这个最大线程数表示线程池曾经出现过的最大线程数
                    if (s > largestPoolSize)
                        largestPoolSize = s; //更新线程池出现过的最大线程数
                    workerAdded = true; //表示工作线程创建成功了
                }
            } finally {
                mainLock.unlock(); //释放锁
            }
            if (workerAdded) { //如果worker添加成功
                t.start(); //启动线程
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w); //如果添加失败,就需要做一件事,就是递减实际工作线程数(还记得我们最开始的时候增加了工作线程数吗)
    }
    return workerStarted; //返回结果
}

分析一下Worker类

//Worker就是线程池中的线程,Wokker本身实现了Runnable,
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
{

    private static final long serialVersionUID = 6138294804551838833L;

    final Thread thread; //注意了,这才是真正执行task的线程,从构造函数可知是由 ThreadFactury 创建的
    
    Runnable firstTask; //这就是需要执行的 task

    volatile long completedTasks; //完成的任务数,用于线程池统计

    Worker(Runnable firstTask) {
        setState(-1); //初始状态 -1,防止在调用 runWorker(),也就是真正执行 task前中断 thread。
        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) {
            }
        }
    }
}

分析addWorkerFailed方法

addWorker 方法中,如果添加 Worker 并且启动线程失败,则会做失败后的处理。 这个方法主要做两件事
1. 如果 worker 已经构造好了,则从 workers 集合中移除这个 worker
2. 原子递减核心线程数(因为在 addWorker 方法中先做了原子增加)
3. 尝试结束线程池
private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)
            workers.remove(w);
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

分析 runWorker 方法

前面已经了解了 ThreadPoolExecutor 的核心方法 addWorker,主要作用是增加工作线程, 而 Worker 简单理解其实就是一个线程,里面重新了 run 方法,这块是线程池中执行任务的 真正处理逻辑,也就是 runWorker 方法,这个方法主要做几件事
1. 如果 task 不为空,则开始执行 task
2. 如果 task 为空,则通过 getTask()再去取任务,并赋值给 task,如果取到的 Runnable 不为空,则 执行该任务
3. 执行完毕后,通过 while 循环继续 getTask()取任务
4. 如果 getTask()取到的任务依然是空,那么整个 runWorker()方法执行完毕
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    
    //unlock,表示当前 worker 线程允许中断,因为 new Worker 默认的 state=-1,此处是调用 Worker 类的 tryRelease()方法,将 state 置为 0,interruptIfStarted()中只有 state>=0 才允许调用中断
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        //注意这个 while 循环,在这里实现了 [线程复用] , 如果 task 为空,则通过 getTask 来获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock(); //上锁,不是为了防止并发执行任务,为了在shutdown()时不终止正
在运行的 worker

            //线程池为 stop 状态时不接受新任务,不执行已经加入任务队列的任务,还中断正在执行的任务
            //所以对于 stop 状态以上是要中断线程的
            //(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP)确保线 程中断标志位为 true 且是 stop 状态以上,接着清除了中断标志
            //!wt.isInterrupted()则再一次检查保证线程需要设置中断标志位
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task); //这里默认是没有实现的,在一些特定的场景中 我们可以自己继承 ThreadpoolExecutor 自己重写
                Throwable thrown = null;
                try {
                    task.run(); //执行任务中的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,需要再通过 getTask()取) + 记录该 Worker 完成任务数量 + 解锁
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        //1.将入参 worker 从数组 workers 里删除掉;
        //2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组 workers
        processWorkerExit(w, completedAbruptly);
    }
}

继续分析getTask方法

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

    for (;;) { //自旋
        int c = ctl.get();
        int rs = runStateOf(c);
        
        // 对线程池状态的判断,两种情况会 workerCount-1,并且返回 null
        //1. 线程池状态为 shutdown,且 workQueue 为空(反映了 shutdown 状态的线程池还是要执行 workQueue 中剩余的任务的)
        //2. 线程池状态为 stop(shutdownNow()会导致变成 STOP)(此时不用考虑 workQueue的情况)
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null; //返回 null,则当前 worker 线程会退出
        }

        int wc = workerCountOf(c);

        // timed变量用于判断是否需要进行超时控制。
        // allowCoreThreadTimeOut默认是false,也就是核心线程不允许进行超时; 
        // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;
        // 对于超过核心线程数量的这些线程,需要进行超时控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        //1. 线程数量超过maximumPoolSize可能是线程池在运行时被调用了setMaximumPoolSize() 被改变了大小,否则已经 addWorker()成功不会超过 maximumPoolSize
        //2. timed && timedOut 如果为 true,表示当前操作需要进行超时控制,并且上次从阻塞队列中 获取任务发生了超时.其实就是体现了空闲线程的存活时间
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            //根据 timed 来判断,如果为 true,则通过阻塞队列 poll 方法进行超时控制,如果在 keepaliveTime 时间内没有获取到任务,则返回 null.
            //否则通过 take 方法阻塞式获取队列中的任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null) //如果拿到的任务不为空,则直接返回给worker进行处理
                return r;
            timedOut = true; // 如果获取任务时当前线程发生了中断,则设置 timedOut 为false 并返回循环重试
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

拒绝策略

1、AbortPolicy:直接抛出异常,默认策略; 
2、CallerRunsPolicy:用调用者所在的线程来执行任务; 
3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务; 
4、DiscardPolicy:直接丢弃任务;当然也可以根据应用场景实现 RejectedExecutionHandler 接口,
    自定义饱和策略,如记录 日志或持久化存储不能处理的任务

线程池注意事项

阿里开发手册不建议使用线程池?
手册上是说线程池的构建不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。
分析完原理以后,大家自己一定要有一个答案。我来简单分析下,用 Executors 使得用户不 需要关心线程池的参数配置,意味着大家对于线程池的运行规则也会慢慢的忽略。
这会导致 一个问题,比如我们用 newFixdThreadPool 或者 singleThreadPool.允许的队列长度为 Integer.MAX_VALUE,如果使用不当会导致大量请求堆积到队列中导致 OOM 的风险
而 newCachedThreadPool,允许创建线程数量为 Integer.MAX_VALUE,也可能会导致大量 线程的创建出现 CPU 使用过高或者 OOM 的问题

如何合理配置线程池的大小

如何合理配置线程池的大小
如何合理配置线程池大小,也是很多同学反馈给我的问题,我也简单说一下。线程池大小不 是靠猜,也不是说越多越好。
在遇到这类问题时,先冷静下来分析
1. 需要分析线程池执行的任务的特性: CPU 密集型还是 IO 密集型
2. 每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉 及到网络传输以及底层系统资源依赖有关系
---------
CPU 密集型还是 IO 密集型是 任务的特性

如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这种任务 cpu 的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU 核心数=最大同时执行 线程数,加入 CPU 核心数为 4,那么服务器最多能同时执行 4 个线程。过多的线程会导致上 下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数+1
如果是 IO 密集型,主要是进行 IO 操作,执行 IO 操作的时间较长,这是 cpu 出于空闲状态, 导致 cpu 的利用率不高,这种情况下可以增加线程池的大小。这种情况下可以结合线程的等 待时长来做判断,等待时间越高,那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。 一个公式:线程池设定最佳线程数目 = (线程池设定的线程等待时间+线程 CPU 时间)/ 线程 CPU 时间 )* CPU 数目
这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用 loadrunner 测试大量运行次数求出平均值)

submit 和 execute / future,futuretask

execute 和 submit 区别
1. execute 只可以接收一个 Runnable 的参数;  
   submit可以接收 Runable 和 Callable 这两种类型的参数
2. execute 如果出现异常会抛出;  
   submit方法调用不会抛异常,除非调用 Future.get()
3. execute 没有返回值;  
   对于submit 方法,如果传入一个Callable,可以得到一个 Future 的返回值


public class Test {

    public static void main(String[] args) throws Exception {

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000l);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("这里是任务1");
            }
        });

        Future future2 = executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(4000l);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("这里是任务2");
            }
        });
        if(future2.get() == null){ //这里对进行一个阻塞
            System.out.println("这里是任务2的结束");
        }

        Future future3 = executorService.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(5000l);
                System.out.println("这里是任务3");
                return 8700;
            }
        });
        if(future3.get() != null){ //这里对进行一个阻塞
            System.out.println("这里是任务3的执行结果.." + future3.get() );
        }
        
        //public class FutureTask<V> implements RunnableFuture<V>
        //public interface RunnableFuture<V> extends Runnable, Future<V>
        //所以FutureTask其实是一个Runnable
        //FutureTask和Future可以实现同样的功能,在这一点没有本质区别,FutureTask是Future的实现类
        FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {

                System.out.println("哈哈哈,我是FutureTask....Begin");
                Thread.sleep(3000l);
                System.out.println("哈哈哈,我是FutureTask....End");
                return "我回来了";
            }
        });
        executorService.submit(futureTask);
        System.out.println("哈哈哈,正常获取FutureTask...的结果");
        if(futureTask.get() != null){
            System.out.println("FutureTask的返回值为:"+futureTask.get());
        }

        System.out.println("我是主线程....");

        executorService.shutdown();
    }
}

运行如下:

image.png