JAVA---线程池(源码分析)

问题是最好的老师。

一、思考

  • 问题一:为什么需要使用线程池?
  • 问题二:线程池的工作原理?
  • 问题三:线程池是如何做到线程复用的?
  • 问题四:当无任何任务执行时,线程池中的线程数量是等于核心线程数还是0?最大线程数是如何降为核心线程数的?
  • 问题五:线程池有哪些状态,当线程池停止后,已经添加的任务如何处理,即将添加的任务如何处理?
  • 问题六:线程池中的阻塞队列以及其实现原理是什么,该如何选择?
  • 问题七:线程池中具有哪些拒绝策略,有什么区别?
  • 问题八:有哪些使用线程池的方式?
  • 问题九:如何合理配置线程池?

备注:问题统一在最后进行总结,然后在源码分析的过程中会穿插分析。

二、源码分析

1.线程池状态和工作线程数(ctl分析)

//通过该变量同时存储线程池状态和工作线程数量,通过runStateOf(ctl)获取状态,workerCountOf(ctl)获取工作线程数量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//32-3=29,线程池状态用int类型32位二进制的高3位表示,请看下文。
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池最大容量:0001 1111 1111 1111 1111 1111 1111 1111,即最高三位用来存储线程池状态,剩下29位用来存储线程池数量。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
//线程池共五种状态,分别用int的高三位表示。具体状态的意义等看完源码之后,在思考的总结中再做解释。
//1110 0000 0000 0000 0000 0000 0000 0000,即111(负数用补码表示)。
private static final int RUNNING    = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000,即000。
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000,即001。
private static final int STOP       =  1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000,即010。
private static final int TIDYING    =  2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000,即011。
private static final int TERMINATED =  3 << COUNT_BITS;

 

// Packing and unpacking ctl
//这里主要借助了如下思想:
//1.X&1=X,即1和任何数X逻辑与等于X。
//2.X|0=X,即0和任何数X逻辑异或等于X。

//已知CAPACITY=0001 1111 1111 1111 1111 1111 1111 1111,则~CAPACITY=1110 0000 0000 0000 0000 0000 0000 0000 

//通过ctl获取线程池运行状态,高三位。请借助思想1自己分析。
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//通过ctl获取工作线程数量,低29位。请借助思想1自己分析。
private static int workerCountOf(int c)  { return c & CAPACITY; }
//将运行状态和工作线程数量打包成一个字段。请借助思想2自己分析。
private static int ctlOf(int rs, int wc) { return rs | wc; }

2.核心字段

//是否允许核心线程超时,可以理解为线程池进行线程服用就是复用的核心线程,其复用的原理就是如果没有任务需要执行时,则阻塞该线程,这个参数控制是否允许阻塞超时。
private volatile boolean allowCoreThreadTimeOut;

//如果设置了allowCoreThreadTimeOut为true或者当工作线程数大于核心线程数时,线程阻塞等待时间,过了这个时间如果还没有任务执行,则该线程结束。
private volatile long keepAliveTime;

//核心线程数量。
private volatile int corePoolSize;

//最大线程数量。
private volatile int maximumPoolSize;

//当工作线程数到达核心线程数时,将提交的任务添加到阻塞队列。
private final BlockingQueue workQueue;

//线程工厂,可以自定义线程池。
private volatile ThreadFactory threadFactory;

//拒绝策略,当工作线程数大于最大线程数时,线程池对于提交的任务采用的处理方式。
private volatile RejectedExecutionHandler handler;

3.工作原理

//执行提交的任务
//command:要执行的任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
  //如果工作线程数量小于核心线程数,则开启新的线程处理该任务。
if (workerCountOf(c) < corePoolSize) {
  //开启新的线程,请看addWorker方法。
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(当线程池设置了允许阻塞超时,且已经超时),添加一个work执行队列中的任务。
  //1.线程池仍然处于运行状态。
  //2.线程池处于关闭状态。但是将任务移出队列失败,此时队列不为空。
else if (workerCountOf(recheck) == 0)
  //addWorker方法中出现的firstTask为null的情况在这里。
addWorker(null, false);
}
  //当队列中已满时,采用最大线程数进行判断。如果工作线程数小于最大线程数。则继续开启一个线程处理。否则采用拒绝策略处理。
else if (!addWorker(command, false))
reject(command);
}

 

4.Worker类

其继承体系如下:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
  • 重要字段
/** Thread this worker is running in.  Null if factory fails. */
//所要开启的线程。该线程会被复用。
final Thread thread;
/** Initial task to run.  Possibly null. */
//第一个将要执行的任务。
Runnable firstTask;
  • 构造函数
Worker(Runnable firstTask) {
    //禁止中断直到运行了Worker。具体请看下面interruptIfStarted方法。
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    //开启一个新的线程。
    this.thread = getThreadFactory().newThread(this);
}
  • 重要方法
public void run() {
    //调用了外部类的runWorker方法。
    runWorker(this);
}
//运行Worker。
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //将state设置为0(构造函数中设置为-1),允许中断。
    w.unlock(); // allow interrupts
    //突然完成。
    boolean completedAbruptly = true;
    try {
        //线程复用的原理即在这里,循环判断:
        //1.如果firstTask不为空,先执行第一个任务。
        //2.firstTask为空,队列中任务不为空,执行队列中的任务。具体请看getTask()方法,该方法实现了线程阻塞。
        //3.如果未阻塞或者阻塞时间已到且从队列中未获取到任务任务,则该线程终止。
        while (task != null || (task = getTask()) != null) {
            //设置state为1,独占锁。
            w.lock();
            //再次判断线程池状态,如果线程池状态大于等于STOP且该线程未被中断,则中断该线程。
            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);
    }
}
//线程中断只能在state大于等于0才可以。
void interruptIfStarted() {
    Thread t;
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}
//从队列中获取任务并根据线程池参数判断是否进行阻塞。
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.
       //判断线程池状态,大于等于STOP,即使队列中有任务也不执行。
       //如果状态为SHUTDOWN,队列不为空,继续执行,为空不执行。
       //当该线程不再执行任何任务时,减少工作线程数。
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        //线程是否会阻塞超时,当存在以下情况则认为会超时。
        //1.设置了允许核心线程数超时。
        //2.当工作线程数大于核心线程数(这里可以解释问题四中的最大线程数是如何降为核心线程数的)。
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        //同时满足以下情况,认为需要将该线程终止:
        //1.工作线程数大于最大线程数或者允许线程阻塞超时且已经阻塞超时。
        //2.工作线程数大于1且队列为空。
        //这里的timedOut第一次进来必然为true,是通过下文的keepAliveTime来综合判断设置的,请看下文。当keepAliceTime时间到,就会变成true。
        //这里工作线程数大于最大线程数,注释解释为due to a call to setMaximumPoolSize,即在线程池执行的过程中,可能重新设置了最大线程数。新设置的值比原值小。
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            //当判断可以终止该线程,但是CAS失败的时候,说明已经有其它线程进行了CAS操作,进行重试。因为可能其它线程CAS操作之后线程数已经不大于核心线程数了,那么如果未设置allowCoreThreadTimeOut为true,该线程就没必要终止。 
            continue;
        }

        try {
            //截止到这里说明线程池状况如下:
            //1.队列不为空。
            //2.该线程还未阻塞超时,即还未进行阻塞操作。
            //3.工作线程数大于最大线程数(为什么出现这种请看上文有解释)。
            //这里通过阻塞队列的两个方法分别实现不超时阻塞和超时阻塞。
            //poll:等待keepAliveTime时间,如果队列中仍然没有任务,则返回为空。
            //take:如果队列中不存在任务,则一直阻塞。
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            //当获取到的任务为空,则说明阻塞时间到,设置timeOut未true,再次循环,就会走到上文的逻辑,成功结束该线程。
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

5.线程池的关闭 

//关闭线程池,使线程池处于SHUTDOWN状态。
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //权限检查,JDK默认的权限检查为空。
        checkShutdownAccess();
       //设置线程池状态为SHUTDOWN。
        advanceRunState(SHUTDOWN);
        //中断空闲的线程,具体请看下文该方法分析。
        interruptIdleWorkers();
        //钩子函数,重写线程池时可以重写该方法。
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    //尝试终止线程池。请看下文对该方法分析。
    tryTerminate();
}
//当worker.tryLock成功时,将该worker视为空闲线程
//因为worker如果为运行状态,该worker必定持有持有锁(即state为1),请看runWorker方法,其调用了lock方法,设置state为1。
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    //中断线程
                    t.interrupt();//中断线程后会发生什么情况。
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}
//尝试将线程池设置为终止TERMINATED状态。
final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        //当存在以下情况时,将线程池推到终态。
        //前提:线程池不为RUNING,线程池并非已经到终止状态。
        //1.线程池为STOP且线程池为空(工作线程数为0)。
        //2.线程池为SHUTDOWN队列为空且线程池为空。
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    //空方法。
                    terminated();
                } finally {
                    //设置为终态。
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}
//关闭线程池,使其处于STOP状态。
public List shutdownNow() {
    List tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        //不同shutdown的是,这里直接中断线程,即使线程是运行的,而shutdown只会中断idle线程。
        interruptWorkers();
        //移出阻塞队列中的所有任务。
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    //见上文,尝试终止线程池。
    tryTerminate();
    return tasks;
}

6.线程池的拒绝策略

线程池共提供了四种拒绝策略,都实现RejectedExecutionHandler接口,重写rejectedExecution方法,默认的拒绝策略为AbortPolicy,接下来我们看一下各种策略的实现方式和不同点:

AbortPolicy

//直接抛出异常
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
}

CallersRunPolicy

//如果线程池未关闭,则由提交该任务的线程执行该任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
}

DiscardOldstPolicy

//如果线程池未关闭,则将阻塞队列队头任务移出,然后重新提交任务到线程池
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
}

DiscardPolicy

//直接放弃执行该任务
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}

三、思考总结

  • 问题一:为什么需要使用线程池?

答:主要有:

1.复用线程。避免频繁创建和销毁线程所带来的开销。

2.更好的管理线程,使系统可控。

  • 问题二:线程池的工作原理?

答:源码中已经分析过。可以自己根据源码分析画个流程图出来。

  • 问题三:线程池是如何做到线程复用的?

答:具体请看getTask()方法,首先取决于我们是否设置了allowCoreThreadTimeOut参数,如过设置为true,那么并不能真正做到线程始终复用,因为如果设置该参数,当核心线程在队列中等待keepAliveTime时间之后仍未获取到任务,核心线程变会终止。当未设置该参数时,核心线程会通过BlockQueue.take()方法一直阻塞等待有任务达到,而且当任务达到之后,通过死循环继续获取,从而达到复用的目的。还有一点就是只调用了一次Worker.thread.start(),而每一个任务的运行都是通过调用Runnable的run()方法执行的。

  • 问题四:当无任何任务执行时,线程池中的线程数量是等于核心线程数还是0?最大线程数是如何降为核心线程数的?

答:当设置了allowCoreThreadTimeOut为true,会变为0,否则会变为核心线程数。最大线程数降为核心线程数在上面源码分析过程中已经讲解,主要在getTask()方法中,判断线程是否允许超时时,有一个条件为wc>corePoolSize,即工作线程数大于核心线程数,即在这种情况下,当前线程变化阻塞超时,最终终止。

  • 问题五:线程池有哪些状态,当线程池停止后,已经添加的任务如何处理,即将添加的任务如何处理?

通过源码分析,我们看到了线程有两个方法可以终止线程池,分别是shutdown()和shutdownNow()方法。其分别把线程置于SHUTDOWN和STOP状态。而正常的状态为RUNNING状态。所以可以从三个状态分析:

1.RUNNING:正常状态,走正常线程池执行流程。

2.SHUTDOWN:关闭状态,该状态下,新提交的任务将拒绝,但仍然会执行已经存在于阻塞队列中的任务。

3.STOP及以上:终止状态,拒绝新任务,中断所有线程即使正在运行,清空队列。

  • 问题六:线程池中的阻塞队列以及其实现原理是什么,该如何选择?

具体请看博文:【阻塞队列】(https://blog.csdn.net/qq_31331965/article/details/100940842)。

  • 问题七:线程池中具有哪些拒绝策略,有什么区别?

请看上文线程池执行策略。

  • 问题八:有哪些使用线程池的方式?

1.通过ThreadPoolExecutor构造函数手动创建线程池。

2.通过Executors的静态方法构造特定需求的线程池。具体请看博文:Executros类的使用。

  • 问题九:如何合理配置线程池?

具体请看参考博文:http://ifeve.com/how-to-calculate-threadpool-size/。

 

你可能感兴趣的:(线程池,java,并发,杂记---并发)