引出线程池

线程是并发编程的基础,前面的文章里,我们的实例基本都是基于线程开发作为实例,并且都是使用的时候就创建一个线程。这种方式比较简单,但是存在一个问题,那就是线程的数量问题。

假设有一个系统比较复杂,需要的线程数很多,如果都是采用这种方式来创建线程的话,那么就会极大的消耗系统资源。首先是因为线程本身的创建和销毁需要时间,如果每个小任务都创建一个线程,那么就会大大降低系统的效率。其次是线程本身也是占用内存空间的,大量的线程运行会抢占内存资源,处理不当很可能会内存溢出,这显然不是我们想看到的。

那么有什么办法解决呢?有一个好的思路就是对线程进行复用,因为所有的线程并不都是同一时间一起运行的,有些线程在某个时刻可能是空闲状态,如果这部分空闲线程能有效利用起来,那么就能让线程的运行被充分的利用,这样就不需要创建那么多的线程了。我们可以把特定数量的线程放在一个容器里,需要使用线程时,从容器里拿出空闲线程使用,线程工作完后不急着关闭,而是退回到线程池等待使用。这样的容器一般被称为线程池。用线程池来管理线程是非常有效的方法,用一张图片可以简单的展示出线程池的管理流程:

Executor框架

Java中也有一套框架来控制管理线程,那就是Executor框架。Executor框架是JDK1.5之后才引入的,位于java.util.cocurrent 包下,可以通过该框架来控制线程的启动、执行和关闭,从而简化并发编程的操作,这是它的核心成员类图:

Executor:最上层的接口,定义了一个基本方法execute,接受一个Runnable参数,用来替代通常创建或启动线程的方法。

ExecutorService:继承自Executor接口,提供了处理多线程的方法。

ScheduledExecutorService:定时调度接口,继承自ExecutorService。

AbstractExecutorService:执行框架的抽象类。

ThreadPoolExecutor:线程池中最核心的一个类,提供了线程池操作的基本方法。

Executors:线程池工厂类,可用于创建一系列有特定功能的线程池。

ThreadPoolExecutor详解

以上Executor框架中的基本成员,其中最核心的的成员无疑就是ThreadPoolExecutor,想了解Java中线程池的运行机制,就必须先了解这个类,而最好的了解方式无疑就是看源码。

构造函数

打开ThreadPoolExecutor的源码,发现类中提供了四个构造方法

  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue) {
  6. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  7. Executors.defaultThreadFactory(), defaultHandler);
  8. }
  9. public ThreadPoolExecutor(int corePoolSize,
  10. int maximumPoolSize,
  11. long keepAliveTime,
  12. TimeUnit unit,
  13. BlockingQueue<Runnable> workQueue,
  14. ThreadFactory threadFactory) {
  15. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  16. threadFactory, defaultHandler);
  17. }
  18. public ThreadPoolExecutor(int corePoolSize,
  19. int maximumPoolSize,
  20. long keepAliveTime,
  21. TimeUnit unit,
  22. BlockingQueue<Runnable> workQueue,
  23. RejectedExecutionHandler handler) {
  24. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  25. Executors.defaultThreadFactory(), handler);
  26. }
  27. public ThreadPoolExecutor(int corePoolSize,
  28. int maximumPoolSize,
  29. long keepAliveTime,
  30. TimeUnit unit,
  31. BlockingQueue<Runnable> workQueue,
  32. ThreadFactory threadFactory,
  33. RejectedExecutionHandler handler) {
  34. if (corePoolSize < 0 ||
  35. maximumPoolSize <= 0 ||
  36. maximumPoolSize < corePoolSize ||
  37. keepAliveTime < 0)
  38. throw new IllegalArgumentException();
  39. if (workQueue == null || threadFactory == null || handler == null)
  40. throw new NullPointerException();
  41. this.corePoolSize = corePoolSize;
  42. this.maximumPoolSize = maximumPoolSize;
  43. this.workQueue = workQueue;
  44. this.keepAliveTime = unit.toNanos(keepAliveTime);
  45. this.threadFactory = threadFactory;
  46. this.handler = handler;
  47. }

可以看出,ThreadPoolExecutor的构造函数中的参数还是比较多的,并且最核心的是第四个构造函数,其中完成了底层的初始化工作。

下面解释一下构造函数参数的含义:

  • corePoolSize:线程池的基本大小。当提交一个任务到线程池后,线程池会创建一个线程执行任务,重复这种操作,直到线程池中的数目达到corePoolSize后不再创建新线程,而是把任务放到缓存队列中。

  • maximumPoolSize:线程池允许创建的最大线程数。

  • workQueue:阻塞队列,用于存储等待执行的任务,并且只能存储调用execute 方法提交的任务。常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。

  • keepAliveTime:线程池中线程的最大空闲时间,这种情况一般是线程数目大于任务的数量导致。

  • unit:keepAliveTime的时间单位,TimeUnit是一个枚举类型,位于java.util.concurrent包下。

  • threadFactory:线程工厂,用于创建线程。

  • handler:拒绝策略,当任务太多来不及处理时所采用的处理策略。

重要的变量

看完了构造函数,我们来看下ThreadPoolExecutor类中几个重要的成员变量:

  1. private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  2. private static final int COUNT_BITS = Integer.SIZE - 3;
  3. private static final int CAPACITY = (1 << COUNT_BITS) - 1;
  4. // runState is stored in the high-order bits
  5. private static final int RUNNING = -1 << COUNT_BITS;
  6. private static final int SHUTDOWN = 0 << COUNT_BITS;
  7. private static final int STOP = 1 << COUNT_BITS;
  8. private static final int TIDYING = 2 << COUNT_BITS;
  9. private static final int TERMINATED = 3 << COUNT_BITS;
  10. // Packing and unpacking ctl
  11. private static int runStateOf(int c) { return c & ~CAPACITY; }
  12. private static int workerCountOf(int c) { return c & CAPACITY; }
  13. private static int ctlOf(int rs, int wc) { return rs | wc; }

ctl:控制线程运行状态的一个字段。同时,根据下面的几个方法runStateOfworkerCountOfctlOf可以看出,该字段还包含了两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),并且使用的是Integar类型,高3位保存runState,低29位保存workerCount。

COUNT_BITS:值为29的常量,在字段CAPACITY被引用计算。

CAPACITY:表示有效线程数量(workerCount)的上限,大小为 (1<<29) - 1。

下面5个变量表示的是线程的运行状态,分别是:

  • RUNNING :接受新提交的任务,并且能处理阻塞队列中的任务;
  • SHUTDOWN:不接受新的任务,但会执行队列中的任务。
  • STOP:不接受新任务,也不处理队列中的任务,同时中断正在处理任务的线程。
  • TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
  • TERMINATED:terminated( ) 方法执行完毕。

用一个状态转换图表示大概如下 (图片来源于https://www.cnblogs.com/liuzhihu/p/8177371.html):

构造函数和基本参数都了解后,接下来就是对类中重要方法的研究了。

线程池执行流程

execute方法

ThreadPoolExecutor类的核心调度方法是execute(),通过调用这个方法可以向线程池提交一个任务,交由线程池去执行。而ThreadPoolExecutor的工作逻辑也可以藉由这个方法来一步步理清。这是方法的源码:

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. //获取ctl的值,前面说了,该值记录着runState和workerCount
  5. int c = ctl.get();
  6. /*
  7. * 调用workerCountOf得到当前活动的线程数;
  8. * 当前活动线程数小于corePoolSize,新建一个线程放入线程池中;
  9. * addWorker(): 把任务添加到该线程中。
  10. */
  11. if (workerCountOf(c) < corePoolSize) {
  12. if (addWorker(command, true))
  13. return;
  14. //如果上面的添加线程操作失败,重新获取ctl值
  15. c = ctl.get();
  16. }
  17. //如果当前线程池是运行状态,并且往工作队列中添加该任务
  18. if (isRunning(c) && workQueue.offer(command)) {
  19. int recheck = ctl.get();
  20. /*
  21. * 如果当前线程不是运行状态,把任务从队列中移除
  22. * 调用reject(内部调用handler)拒绝接受任务
  23. */
  24. if (! isRunning(recheck) && remove(command))
  25. reject(command);
  26. //获取线程池中的有效线程数,如果为0,则执行addWorker创建一个新线程
  27. else if (workerCountOf(recheck) == 0)
  28. addWorker(null, false);
  29. }
  30. /*
  31. * 如果执行到这里,有两种情况:
  32. * 1. 线程池已经不是RUNNING状态;
  33. * 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
  34. * 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;
  35. * 如果失败则拒绝该任务
  36. */
  37. else if (!addWorker(command, false))
  38. reject(command);
  39. }

简单概括一下代码的逻辑,大概是这样:

1、判断当前运行中的线程数是否小于corePoolSize,是的话则调用addWorker创建线程执行任务。

2、不满足1的条件,就把任务放入工作队列workQueue中。

3、如果任务成功加入workQueue,判断线程池是否是运行状态,不是的话先把任务移出工作队列,并调用reject方法,使用拒绝策略拒绝该任务。线程如果是非运行中,调用addWorker创建一个新线程。

4、如果放入workQueue失败 (队列已满),则调用addWorker创建线程执行任务,如果这时创建线程失败 (addWorker传进去的第二个参数值是false,说明这种情况是当前线程数不小于maximumPoolSize),就会调用reject(内部调用handler)拒绝接受任务。

整个执行流程用一张图片表示大致如下:

以上就是execute方法的大概逻辑,接下来看看addWorker的方法实现。

addWorker方法

源码如下:

  1. private boolean addWorker(Runnable firstTask, boolean core) {
  2. retry:
  3. for (;;) {
  4. int c = ctl.get();
  5. int rs = runStateOf(c);
  6. /**线程池状态不为SHUTDOWN时
  7. * 判断队列或者任务是否为空,是的话返回false
  8. */.
  9. if (rs >= SHUTDOWN &&
  10. ! (rs == SHUTDOWN &&
  11. firstTask == null &&
  12. ! workQueue.isEmpty()))
  13. return false;
  14. for (;;) {
  15. int wc = workerCountOf(c);
  16. /* 这里可以看出core参数决定着活动线程数的大小比较对象
  17. * core为true表示与 corePoolSize大小进行比较
  18. * core为false表示与 maximumPoolSize大小进行比较
  19. * 当前活动线程数大于比较对象就返回false
  20. */
  21. if (wc >= CAPACITY ||
  22. wc >= (core ? corePoolSize : maximumPoolSize))
  23. return false;
  24. // 尝试增加workerCount,如果成功,则跳出第一个for循环
  25. if (compareAndIncrementWorkerCount(c))
  26. break retry;
  27. // 如果增加workerCount失败,则重新获取ctl的值
  28. c = ctl.get(); // Re-read ctl
  29. // 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行
  30. if (runStateOf(c) != rs)
  31. continue retry;
  32. // else CAS failed due to workerCount change; retry inner loop
  33. }
  34. }
  35. boolean workerStarted = false;
  36. boolean workerAdded = false;
  37. Worker w = null;
  38. try {
  39. //创建一个worker对象w
  40. w = new Worker(firstTask);
  41. //实例化w的线程t
  42. final Thread t = w.thread;
  43. if (t != null) {
  44. final ReentrantLock mainLock = this.mainLock;
  45. mainLock.lock();
  46. try {
  47. // Recheck while holding lock.
  48. // Back out on ThreadFactory failure or if
  49. // shut down before lock acquired.
  50. int rs = runStateOf(ctl.get());
  51. if (rs < SHUTDOWN ||
  52. (rs == SHUTDOWN && firstTask == null)) {
  53. if (t.isAlive()) // precheck that t is startable
  54. throw new IllegalThreadStateException();
  55. // workers是一个HashSet,保存着任务的worker对象
  56. workers.add(w);
  57. int s = workers.size();
  58. if (s > largestPoolSize)
  59. largestPoolSize = s;
  60. workerAdded = true;
  61. }
  62. } finally {
  63. mainLock.unlock();
  64. }
  65. if (workerAdded) {
  66. //启动线程
  67. t.start();
  68. workerStarted = true;
  69. }
  70. }
  71. } finally {
  72. if (! workerStarted)
  73. addWorkerFailed(w);
  74. }
  75. return workerStarted;
  76. }

从代码中可以看出,addWorker方法的主要工作是在线程池中创建一个新的线程并执行,其中firstTask参数指定的是新线程需要执行的第一个任务,core参数决定于活动线程数的比较对象是corePoolSize还是maximumPoolSize。根据传进来的参数首先对线程池和队列的状态进行判断,满足条件就新建一个Worker对象,并实例化该对象的线程,最后启动线程。

Worker类

根据addWorker源码中的逻辑,我们可以发现,线程池中的每一个线程其实都是对应的Worker对象在维护的,所以我们有必要对Worker类一探究竟,先看一下类的源码:

  1. private final class Worker
  2. extends AbstractQueuedSynchronizer
  3. implements Runnable
  4. {
  5. /**
  6. * This class will never be serialized, but we provide a
  7. * serialVersionUID to suppress a javac warning.
  8. */
  9. private static final long serialVersionUID = 6138294804551838833L;
  10. /** Thread this worker is running in. Null if factory fails. */
  11. final Thread thread;
  12. /** Initial task to run. Possibly null. */
  13. Runnable firstTask;
  14. /** Per-thread task counter */
  15. volatile long completedTasks;
  16. /**
  17. * Creates with given first task and thread from ThreadFactory.
  18. * @param firstTask the first task (null if none)
  19. */
  20. Worker(Runnable firstTask) {
  21. setState(-1); // inhibit interrupts until runWorker
  22. this.firstTask = firstTask;
  23. this.thread = getThreadFactory().newThread(this);
  24. }
  25. /** Delegates main run loop to outer runWorker */
  26. public void run() {
  27. runWorker(this);
  28. }
  29. // Lock methods
  30. //
  31. // The value 0 represents the unlocked state.
  32. // The value 1 represents the locked state.
  33. protected boolean isHeldExclusively() {
  34. return getState() != 0;
  35. }
  36. protected boolean tryAcquire(int unused) {
  37. if (compareAndSetState(0, 1)) {
  38. setExclusiveOwnerThread(Thread.currentThread());
  39. return true;
  40. }
  41. return false;
  42. }
  43. protected boolean tryRelease(int unused) {
  44. setExclusiveOwnerThread(null);
  45. setState(0);
  46. return true;
  47. }
  48. public void lock() { acquire(1); }
  49. public boolean tryLock() { return tryAcquire(1); }
  50. public void unlock() { release(1); }
  51. public boolean isLocked() { return isHeldExclusively(); }
  52. void interruptIfStarted() {
  53. Thread t;
  54. if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
  55. try {
  56. t.interrupt();
  57. } catch (SecurityException ignore) {
  58. }
  59. }
  60. }
  61. }

从Worker类的构造函数可以看出,当实例化一个Worker对象时,Worker对象会把传进来的Runnable参数firstTask赋值给自己的同名属性,并且用线程工厂也就是当前的ThreadFactory来新建一个线程。

同时,因为Worker实现了Runnable接口,所以当Worker类中的线程启动时,调用的其实是run()方法。run方法中调用的是runWorker方法,我们来看下它的具体实现:

  1. final void runWorker(Worker w) {
  2. Thread wt = Thread.currentThread();
  3. //获取第一个任务
  4. Runnable task = w.firstTask;
  5. w.firstTask = null;
  6. //允许中断
  7. w.unlock(); // allow interrupts
  8. //是否因为异常退出循环的标志,processWorkerExit方法会对该参数做判断
  9. boolean completedAbruptly = true;
  10. try {
  11. //判断task是否为null,是的话通过getTask()从队列中获取任务
  12. while (task != null || (task = getTask()) != null) {
  13. w.lock();
  14. // If pool is stopping, ensure thread is interrupted;
  15. // if not, ensure thread is not interrupted. This
  16. // requires a recheck in second case to deal with
  17. // shutdownNow race while clearing interrupt
  18. /* 这里的判断主要逻辑是这样:
  19. * 如果线程池正在停止,那么就确保当前线程是中断状态;
  20. * 如果不是的话,就要保证不是中断状态
  21. */
  22. if ((runStateAtLeast(ctl.get(), STOP) ||
  23. (Thread.interrupted() &&
  24. runStateAtLeast(ctl.get(), STOP))) &&
  25. !wt.isInterrupted())
  26. wt.interrupt();
  27. try {
  28. //用于记录任务执行前需要做哪些事,属于ThreadPoolExecutor类中的方法, //是空的,需要子类具体实现
  29. beforeExecute(wt, task);
  30. Throwable thrown = null;
  31. try {
  32. //执行任务
  33. task.run();
  34. } catch (RuntimeException x) {
  35. thrown = x; throw x;
  36. } catch (Error x) {
  37. thrown = x; throw x;
  38. } catch (Throwable x) {
  39. thrown = x; throw new Error(x);
  40. } finally {
  41. afterExecute(task, thrown);
  42. }
  43. } finally {
  44. task = null;
  45. w.completedTasks++;
  46. w.unlock();
  47. }
  48. }
  49. completedAbruptly = false;
  50. } finally {
  51. processWorkerExit(w, completedAbruptly);
  52. }
  53. }

总结一下runWorker方法的运行逻辑:

1、通过while循环不断地通过getTask()方法从队列中获取任务;

2、如果线程池正在停止状态,确保当前的线程是中断状态,否则确保当前线程不中断;

3、调用task的run()方法执行任务,执行完毕后需要置为null;

4、循环调用getTask()取不到任务了,跳出循环,执行processWorkerExit()方法。

过完runWorker()的运行流程,我们来看下getTask()是怎么实现的。

getTask方法

getTask()方法的作用是从队列中获取任务,下面是该方法的源码:

  1. private Runnable getTask() {
  2. //记录上次从队列获取任务是否超时
  3. boolean timedOut = false; // Did the last poll() time out?
  4. for (;;) {
  5. int c = ctl.get();
  6. int rs = runStateOf(c);
  7. // Check if queue empty only if necessary.
  8. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
  9. //将workerCount减1
  10. decrementWorkerCount();
  11. return null;
  12. }
  13. int wc = workerCountOf(c);
  14. // Are workers subject to culling?
  15. /* timed变量用于判断线程的操作是否需要进行超时判断
  16. * allowCoreThreadTimeOut不管它,默认是false
  17. * wc > corePoolSize,当前线程是如果大于核心线程数corePoolSize
  18. */
  19. boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
  20. if ((wc > maximumPoolSize || (timed && timedOut))
  21. && (wc > 1 || workQueue.isEmpty())) {
  22. if (compareAndDecrementWorkerCount(c))
  23. return null;
  24. continue;
  25. }
  26. try {
  27. /* 根据timed变量判断,如果为true,调用workQueue的poll方法获取任务,
  28. * 如果在keepAliveTime时间内没有获取到任务,则返回null;
  29. * timed为false的话,就调用workQueue的take方法阻塞队列,
  30. * 直到队列中有任务可取。
  31. */
  32. Runnable r = timed ?
  33. workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
  34. workQueue.take();
  35. if (r != null)
  36. return r;
  37. //r为null,说明time为true,超时了,把timedOut也设置为true
  38. timedOut = true;
  39. } catch (InterruptedException retry) {
  40. //发生异常,把timedOut也设置为false,重新跑循环
  41. timedOut = false;
  42. }
  43. }
  44. }

getTask的代码看上去比较简单,但其实内有乾坤,我们来重点分析一下两个if判断的逻辑:

1、当进入getTask方法后,先判断当前线程池状态,如果线程池状态rs >= SHUTDOWN,再进行以下判断:

1)rs 的状态是否大于STOP;2)队列是否为空;

满足以上条件其中之一,就将workerCount减1并返回null,也就是表示队列中不再有任务。因为线程池的状态值是SHUTDOWN以上时,队列中不再允许添加新任务,所以上面两个条件满足一个都说明队列中的任务都取完了。

2、进入第二个if判断,这里的逻辑有点绕,但作用比较重要,是为了控制线程池的有效线程数量,我们来具体解析下代码:

wc > maximumPoolSize:判断当前线程数是否大于maximumPoolSize,这种情况一般很少发生,除非是maximumPoolSize的大小在该程序执行的同时被进行设置,比如调用ThreadPoolExecutor中的setMaximumPoolSize方法。

timed && timedOut:如果为true,表示当前的操作需要进行超时判断,并且上次从队列获取任务已经超时。

wc > 1 || workQueue.isEmpty():如果工作线程大于1,或者阻塞队列是空的。

compareAndDecrementWorkerCount:比较并将线程池中的workerCount减1

在上文中,我们解析execute方法的逻辑时了解到,如果当前线程池的线程数量超过了corePoolSize且小于maximumPoolSize,并且workQueue已满时,仍然可以增加工作线程。

但调用getTask()取任务的过程中,如果超时没有获取到任务,也就是timedOut为true的情况,说明workQueue已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于corePoolSize数量的线程销毁掉,也就是不断的让任务被取出,让线程数量保持在corePoolSize即可,直到getTask方法返回null。

而当getTask方法返回null后,runWorker方法中就会因为取不到任务而执行processWorkerExit()方法。

processWorkerExit方法

processWorkerExit方法的作用主要是对worker对象的移除,下面是方法的源码:

  1. private void processWorkerExit(Worker w, boolean completedAbruptly) {
  2. //是异常退出的话,执行程序将workerCount数量减1
  3. if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
  4. decrementWorkerCount();
  5. final ReentrantLock mainLock = this.mainLock;
  6. mainLock.lock();
  7. try {
  8. completedTaskCount += w.completedTasks;
  9. // 从workers的集合中移除worker对象,也就表示着从线程池中移除了一个工作线程
  10. workers.remove(w);
  11. } finally {
  12. mainLock.unlock();
  13. }
  14. tryTerminate();
  15. int c = ctl.get();
  16. if (runStateLessThan(c, STOP)) {
  17. if (!completedAbruptly) {
  18. int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
  19. if (min == 0 && ! workQueue.isEmpty())
  20. min = 1;
  21. if (workerCountOf(c) >= min)
  22. return; // replacement not needed
  23. }
  24. addWorker(null, false);
  25. }
  26. }

至此,从executor方法开始的整个运行过程就完毕了,总结一下该流程:

执行executor --> 新建Worker对象,并实例化线程 --> 调用runWorker方法,通过getTask()获取任务,并执行run方法 --> getTask()方法中不断向队列取任务,并将workerCount数量减1,直至返回null --> 调用processWorkerExit清除worker对象。

用一张流程图表示如下所示 (图片来源于https://www.cnblogs.com/liuzhihu/p/8177371.html):

任务队列workQueue

前面我们多次提到了workQueue,这是一个任务队列,用来存放等待执行的任务,它是BlockingQueue类型的对象,而在ThreadPoolExecutor的源码注释中,详细介绍了三种常用的Queue类型,分别是:

  • SynchronousQueue:直接提交的队列。这个队列没有容量,当接收到任务的时候,会直接提交给线程处理,而不保留它。如果没有空闲的线程,就新建一个线程来处理这个任务!如果线程数量达到最大值,就会执行拒绝策略。所以,使用这个类型队列的时候,一般都是将maximumPoolSize一般指定成Integer.MAX_VALUE,避免容易被拒绝。

  • ArrayBlockingQueue:有界的任务队列。需要给定一个参数来限制队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程 (核心线程) 执行任务,如果达到了,则将任务放入等待队列。如果队列已满,则在总线程数不到maximumPoolSize的前提下新建线程执行任务,若大于maximumPoolSize,则执行拒绝策略。

  • LinkedBlockingQueue:无界的任务队列。该队列没有任务数量的限制,所以任务可以一直入队,知道耗尽系统资源。当接收任务,如果当前线程数小于corePoolSize,则新建线程处理任务;如果当前线程数等于corePoolSize,则进入队列等待。

任务拒绝策略

当线程池的任务队列已满并且线程数目达到maximumPoolSize时,对于新加的任务一般会采取拒绝策略,通常有以下四种策略:

  1. AbortPolicy:直接抛出异常,这是默认策略;
  2. CallerRunsPolicy:用调用者所在的线程来执行任务;
  3. DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  4. DiscardPolicy:直接丢弃任务;

线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow():

  1. public void shutdown() {
  2. final ReentrantLock mainLock = this.mainLock;
  3. mainLock.lock();
  4. try {
  5. checkShutdownAccess();
  6. advanceRunState(SHUTDOWN);
  7. interruptIdleWorkers();
  8. onShutdown(); // hook for ScheduledThreadPoolExecutor
  9. } finally {
  10. mainLock.unlock();
  11. }
  12. tryTerminate();
  13. }
  14. public List<Runnable> shutdownNow() {
  15. List<Runnable> tasks;
  16. final ReentrantLock mainLock = this.mainLock;
  17. mainLock.lock();
  18. try {
  19. checkShutdownAccess();
  20. advanceRunState(STOP);
  21. interruptWorkers();
  22. tasks = drainQueue();
  23. } finally {
  24. mainLock.unlock();
  25. }
  26. tryTerminate();
  27. return tasks;
  28. }

代码逻辑就不一一进行解析了,总结一下两个方法的特点就是:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

ThreadPoolExecutor创建线程池实例

ThreadPoolExecutor的运行机制讲完了,接下来展示一下如何用ThreadPoolExecutor创建线程池实例,具体代码如下:

  1. public static void main(String[] args) {
  2. ExecutorService service = new ThreadPoolExecutor(5, 10, 300, TimeUnit.MILLISECONDS,
  3. new ArrayBlockingQueue<Runnable>(5));
  4. //用lambda表达式编写方法体中的逻辑
  5. Runnable run = () -> {
  6. try {
  7. Thread.sleep(1000);
  8. System.out.println(Thread.currentThread().getName() + "正在执行");
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. };
  13. for (int i = 0; i < 10; i++) {
  14. service.execute(run);
  15. }
  16. //这里一定要做关闭
  17. service.shutdown();
  18. }

上面的代码中,我们创建的ThreadPoolExecutor线程池的核心线程数为5个,所以,当调用线程池执行任务时,同时运行的线程最多也是5个,执行main方法,输出结果如下:

  1. pool-1-thread-3正在执行
  2. pool-1-thread-1正在执行
  3. pool-1-thread-4正在执行
  4. pool-1-thread-5正在执行
  5. pool-1-thread-3正在执行
  6. pool-1-thread-2正在执行
  7. pool-1-thread-1正在执行
  8. pool-1-thread-4正在执行
  9. pool-1-thread-5正在执行

看到出来,线程池确实只有5个线程在工作,也就是真正的实现了线程的复用,说明我们的ThreadPoolExecutor实例是有效的。

参考:

https://www.cnblogs.com/liuzhihu/p/8177371.html

https://www.cnblogs.com/dolphin0520/p/3932921.html

《实战Java:高并发程序设计》

Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析的更多相关文章

  1. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  2. Java并发编程:线程池的使用(转)

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  3. Java并发编程:线程池的使用(转载)

    转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

  4. Java并发编程:线程池的使用(转载)

    文章出处:http://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

  5. [转]Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  6. 【转】Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  7. 13、Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  8. (转)Java并发编程:线程池的使用

    背景:线程池在面试时候经常遇到,反复出现的问题就是理解不深入,不能做到游刃有余.所以这篇博客是要深入总结线程池的使用. ThreadPoolExecutor的继承关系 线程池的原理 1.线程池状态(4 ...

  9. Java并发编程:线程池ThreadPoolExecutor

    多线程的程序的确能发挥多核处理器的性能.虽然与进程相比,线程轻量化了很多,但是其创建和关闭同样需要花费时间.而且线程多了以后,也会抢占内存资源.如果不对线程加以管理的话,是一个非常大的隐患.而线程池的 ...

随机推荐

  1. verilog 有符号数(2转)

    在数字电路中,出于应用的需要,我们可以使用无符号数,即包括0及整数的集合:也可以使用有符号数,即包括0和正负数的集合.在更加复杂的系统中,也许这两种类型的数,我们都会用到. 有符号数通常以2的补码形式 ...

  2. 初识RabbitMQ

    1.安装 rabbitmq官网:http://www.rabbitmq.com/ 下载地址:https://packagecloud.io/rabbitmq 下载rabbitmq-server 安装脚 ...

  3. OpenCV2.4.10 + VS2010开发环境配置

    原文转载自:qinyang8513 一.开发环境 1.操作系统:Windows 7(64位) 2.编程环境:Microsoft Visual Studio 2010 3.OpenCV版本:2.4.10 ...

  4. 读取嵌入到word的Excel对象

    Word.Document doc = this._wordApplication.Documents.Add(@"C:\Users\linmeicheng\Desktop\新建文件夹 (3 ...

  5. Hibernate3.0配置

    我的系统Win10(64x),Eclipse jee 2018-09 ,Sql2018版本. 以下是Hibernate3.0配置包 链接:https://pan.baidu.com/s/10Kizby ...

  6. C# 监听HTTP请求

    先把代码放在这里,下面再详细解说: using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Oracle.DataAccess.Client; ...

  7. web API简介(四):客户端储存之IndexedDB API

    概述 前篇:web API简介(三):客户端储存之Web Storage API 客户端储存从某一方面来说和动态网站差不多.动态网站是用服务端来储存数据,而客户端储存是用客户端来储存数据. Index ...

  8. 正确使用AES对称加密

    正确使用AES对称加密 经常我看到项目中有人使用了对称加密算法,用来加密客户或项目传输中的部分数据.但我注意到开发 人员由于不熟悉原理,或者简单复制网上的代码示例,有导致代码存在安全风险. 我经常遇到 ...

  9. sublime text下安装插件autoprefixer

    有时候在写css样式的时候,分不清哪些属性需要前缀,哪些不需要,总是爱搞混淆了,于是autoprefixer这款插件便应运而生了.虽然在使用webpack的时候,我们可以很方便的使用这个,但是,如果项 ...

  10. LeetCode:94_Binary Tree Inorder Traversal | 二叉树中序遍历 | Medium

    题目:Binary Tree Inorder Traversal 二叉树的中序遍历,和前序.中序一样的处理方式,代码见下: struct TreeNode { int val; TreeNode* l ...