面试线程池

一. 线程池简介

1. 线程池的概念:

          线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

2. 线程池的工作机制

         2.1 在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。

         2.1 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

3. 使用线程池的原因:

        多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了。

二. 四种常见的线程池详解

1. 线程池的返回值ExecutorService简介:

         ExecutorService是Java提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程

2. 具体的4种常用的线程池实现如下:(返回值都是ExecutorService)

         2.1 Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

         示例代码:

 

 1 package com.study.test;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 
 6 public class ThreadPoolExecutorTest {
 7     public static void main(String[] args) {
 8         //创建一个可缓存线程池
 9         ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
10         for (int i = 0; i < 10; i++) {
11             try {
12                 //sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
13                 Thread.sleep(1000);
14             } catch (InterruptedException e) {
15                 e.printStackTrace();
16             }
17             cachedThreadPool.execute(new Runnable() {
18                 public void run() {
19                     //打印正在执行的缓存线程信息
20                     System.out.println(Thread.currentThread().getName()+"正在被执行");
21                 }
22             });
23         }
24     }
25 }

 

输出结果:

pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行
pool-1-thread-1正在被执行

线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程

       2.2  Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。

         示例代码:

 

 1 package com.study.test;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 
 6 public class ThreadPoolExecutorTest {
 7     public static void main(String[] args) {
 8         //创建一个可重用固定个数的线程池
 9         ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
10         for (int i = 0; i < 10; i++) {
11             fixedThreadPool.execute(new Runnable() {
12                 public void run() {
13                     try {
14                         //打印正在执行的缓存线程信息
15                         System.out.println(Thread.currentThread().getName()+"正在被执行");
16                         Thread.sleep(2000);
17                     } catch (InterruptedException e) {
18                         e.printStackTrace();
19                     }
20                 }
21             });
22         }
23     }
24 }

 

输出结果:

pool-1-thread-1正在被执行
pool-1-thread-2正在被执行
pool-1-thread-3正在被执行
pool-1-thread-1正在被执行
pool-1-thread-2正在被执行
pool-1-thread-3正在被执行
pool-1-thread-1正在被执行
pool-1-thread-2正在被执行
pool-1-thread-3正在被执行
pool-1-thread-1正在被执行

因为线程池大小为3,每个任务输出打印结果后sleep 2秒,所以每两秒打印3个结果。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

       2.3  Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行

             延迟执行示例代码:

 

 1 package com.study.test;
 2 
 3 import java.util.concurrent.Executors;
 4 import java.util.concurrent.ScheduledExecutorService;
 5 import java.util.concurrent.TimeUnit;
 6 
 7 public class ThreadPoolExecutorTest {
 8     public static void main(String[] args) {
 9         //创建一个定长线程池,支持定时及周期性任务执行——延迟执行
10         ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
11         //延迟1秒执行
12         scheduledThreadPool.schedule(new Runnable() {
13             public void run() {
14                 System.out.println("延迟1秒执行");
15             }
16         }, 1, TimeUnit.SECONDS);
17     }
18 }

 

输出结果:延迟1秒执行

             定期执行示例代码:

 

 1 package com.study.test;
 2 
 3 import java.util.concurrent.Executors;
 4 import java.util.concurrent.ScheduledExecutorService;
 5 import java.util.concurrent.TimeUnit;
 6 
 7 public class ThreadPoolExecutorTest {
 8     public static void main(String[] args) {
 9         //创建一个定长线程池,支持定时及周期性任务执行——定期执行
10         ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
11         //延迟1秒后每3秒执行一次
12         scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
13             public void run() {
14                 System.out.println("延迟1秒后每3秒执行一次");
15             }
16         }, 1, 3, TimeUnit.SECONDS);
17     }
18 }

 

输出结果:

延迟1秒后每3秒执行一次
延迟1秒后每3秒执行一次
.............

        2.4  Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

               示例代码: 

 

 1 package com.study.test;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 
 6 public class TestThreadPoolExecutor {
 7     public static void main(String[] args) {
 8         //创建一个单线程化的线程池
 9         ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
10         for (int i = 0; i < 10; i++) {
11             final int index = i;
12             singleThreadExecutor.execute(new Runnable() {
13                 public void run() {
14                     try {
15                         //结果依次输出,相当于顺序执行各个任务
16                         System.out.println(Thread.currentThread().getName()+"正在被执行,打印的值是:"+index);
17                         Thread.sleep(1000);
18                     } catch (InterruptedException e) {
19                         e.printStackTrace();
20                     }
21                 }
22             });
23         }
24     }
25 }

 

输出结果:

pool-1-thread-1正在被执行,打印的值是:0
pool-1-thread-1正在被执行,打印的值是:1
pool-1-thread-1正在被执行,打印的值是:2
pool-1-thread-1正在被执行,打印的值是:3
pool-1-thread-1正在被执行,打印的值是:4
pool-1-thread-1正在被执行,打印的值是:5
pool-1-thread-1正在被执行,打印的值是:6
pool-1-thread-1正在被执行,打印的值是:7
pool-1-thread-1正在被执行,打印的值是:8
pool-1-thread-1正在被执行,打印的值是:9

三. 缓冲队列BlockingQueue和自定义线程池ThreadPoolExecutor

1. 缓冲队列BlockingQueue简介:

          BlockingQueue是双缓冲队列。BlockingQueue内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。

2. 常用的几种BlockingQueue:

  • ArrayBlockingQueue(int i):规定大小的BlockingQueue,其构造必须指定大小。其所含的对象是FIFO顺序排序的。

  • LinkedBlockingQueue()或者(int i):大小不固定的BlockingQueue,若其构造时指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE来决定。其所含的对象是FIFO顺序排序的。

  • PriorityBlockingQueue()或者(int i):类似于LinkedBlockingQueue,但是其所含对象的排序不是FIFO,而是依据对象的自然顺序或者构造函数的Comparator决定。

  • SynchronizedQueue():特殊的BlockingQueue,对其的操作必须是放和取交替完成。

3. 自定义线程池(ThreadPoolExecutor和BlockingQueue连用):

     自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池。

    常见的构造函数:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

     示例代码:

 

 1 package com.study.test;
 2 
 3 import java.util.concurrent.ArrayBlockingQueue;
 4 import java.util.concurrent.BlockingQueue;
 5 import java.util.concurrent.ThreadPoolExecutor;
 6 import java.util.concurrent.TimeUnit;
 7 
 8 class TempThread implements Runnable {
 9 
10     @Override
11     public void run() {
12         // 打印正在执行的缓存线程信息
13         System.out.println(Thread.currentThread().getName() + "正在被执行");
14         try {
15             // sleep一秒保证3个任务在分别在3个线程上执行
16             Thread.sleep(1000);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20     }
21 
22 }
23 
24 public class TestThreadPoolExecutor {
25     public static void main(String[] args) {
26         // 创建数组型缓冲等待队列
27         BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
28         // ThreadPoolExecutor:创建自定义线程池,池中保存的线程数为3,允许最大的线程数为6
29         ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 6, 50, TimeUnit.MILLISECONDS, bq);
30 
31         // 创建3个任务
32         Runnable t1 = new TempThread();
33         Runnable t2 = new TempThread();
34         Runnable t3 = new TempThread();
35         // Runnable t4 = new TempThread();
36         // Runnable t5 = new TempThread();
37         // Runnable t6 = new TempThread();
38 
39         // 3个任务在分别在3个线程上执行
40         tpe.execute(t1);
41         tpe.execute(t2);
42         tpe.execute(t3);
43         // tpe.execute(t4);
44         // tpe.execute(t5);
45         // tpe.execute(t6);
46 
47         // 关闭自定义线程池
48         tpe.shutdown();
49     }
50 }

 

输出结果:

pool-1-thread-1正在被执行
pool-1-thread-2正在被执行
pool-1-thread-3正在被执行

四。我们的开发中“池”的概念并不罕见,有数据库连接池、线程池、对象池、常量池等等。下面我们主要针对线程池来一步一步揭开线程池的面纱。

使用线程池的好处

1、降低资源消耗:可以重复利用已创建的线程降低线程创建和销毁造成的消耗。

2、提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。

3、提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

线程池的工作原理

首先我们看下当一个新的任务提交到线程池之后,线程池是如何处理的

1、线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。

2、线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里进行等待。如果工作队列满了,则执行第三步

3、线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务

线程池饱和策略

这里提到了线程池的饱和策略,那我们就简单介绍下有哪些饱和策略:

AbortPolicy

为Java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出。

DiscardPolicy

直接抛弃,任务不执行,空方法

DiscardOldestPolicy

从队列里面抛弃head的一个任务,并再次execute 此task。

CallerRunsPolicy

在调用execute的线程里面执行此command,会阻塞入口

用户自定义拒绝策略(最常用)

实现RejectedExecutionHandler,并自己定义策略模式

下我们以ThreadPoolExecutor为例展示下线程池的工作流程图

面试官常问的线程池,你真的了解吗

 

面试官常问的线程池,你真的了解吗

 

1、如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

2、如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3、如果无法将任务加入BlockingQueue(队列已满),则在非corePool中创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。

4、如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。

线程池只是并发编程中的一小部分,下图是史上最全面的Java的并发编程学习技术总汇

面试官常问的线程池,你真的了解吗

 

关键方法源码分析

我们看看核心方法添加到线程池方法execute的源码如下:

 

//
    //Executes the given task sometime in the future.  The task
    //may execute in a new thread or in an existing pooled thread.
    //
    // If the task cannot be submitted for execution, either because this
    // executor has been shutdown or because its capacity has been reached,
    // the task is handled by the current {@code RejectedExecutionHandler}.
    //
    // @param command the task to execute
    // @throws RejectedExecutionException at discretion of
    //         {@code RejectedExecutionHandler}, if the task
    //         cannot be accepted for execution
    // @throws NullPointerException if {@code command} is null
    //
   public void execute(Runnable command) {
       if (command == null)
           throw new NullPointerException();
       //
        // Proceed in 3 steps:
        //
        // 1. If fewer than corePoolSize threads are running, try to
        // start a new thread with the given command as its first
        // task.  The call to addWorker atomically checks runState and
        // workerCount, and so prevents false alarms that would add
        // threads when it shouldn't, by returning false.
        // 翻译如下:
        // 判断当前的线程数是否小于corePoolSize如果是,使用入参任务通过addWord方法创建一个新的线程,
        // 如果能完成新线程创建exexute方法结束,成功提交任务
        // 2. If a task can be successfully queued, then we still need
        // to double-check whether we should have added a thread
        // (because existing ones died since last checking) or that
        // the pool shut down since entry into this method. So we
        // recheck state and if necessary roll back the enqueuing if
        // stopped, or start a new thread if there are none.
        // 翻译如下:
        // 在第一步没有完成任务提交;状态为运行并且能否成功加入任务到工作队列后,再进行一次check,如果状态
        // 在任务加入队列后变为了非运行(有可能是在执行到这里线程池shutdown了),非运行状态下当然是需要
        // reject;然后再判断当前线程数是否为0(有可能这个时候线程数变为了0),如是,新增一个线程;
        // 3. If we cannot queue task, then we try to add a new
        // thread.  If it fails, we know we are shut down or saturated
        // and so reject the task.
        // 翻译如下:
        // 如果不能加入任务到工作队列,将尝试使用任务新增一个线程,如果失败,则是线程池已经shutdown或者线程池
        // 已经达到饱和状态,所以reject这个他任务
        //
       int c = ctl.get();
       // 工作线程数小于核心线程数
       if (workerCountOf(c) < corePoolSize) {
           // 直接启动新线程,true表示会再次检查workerCount是否小于corePoolSize
           if (addWorker(command, true))
               return;
           c = ctl.get();
       }
       // 如果工作线程数大于等于核心线程数
       // 线程的的状态未RUNNING并且队列notfull
       if (isRunning(c) && workQueue.offer(command)) {
           // 再次检查线程的运行状态,如果不是RUNNING直接从队列中移除
           int recheck = ctl.get();
           if (! isRunning(recheck) && remove(command))
               // 移除成功,拒绝该非运行的任务
               reject(command);
           else if (workerCountOf(recheck) == 0)
               // 防止了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。
               // 添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务
               addWorker(null, false);
       }
       // 如果队列满了或者是非运行的任务都拒绝执行
       else if (!addWorker(command, false))
           reject(command);
   }

下面我们继续看看addWorker是如何实现的:

 

private boolean addWorker(Runnable firstTask, boolean core) {
       // java标签
       retry:
       // 死循环
       for (;;) {
           int c = ctl.get();
           // 获取当前线程状态
           int rs = runStateOf(c);
           // Check if queue empty only if necessary.
           // 这个逻辑判断有点绕可以改成
           // rs >= shutdown && (rs != shutdown || firstTask != null || workQueue.isEmpty())
           // 逻辑判断成立可以分为以下几种情况均不接受新任务
           // 1、rs > shutdown:--不接受新任务
           // 2、rs >= shutdown && firstTask != null:--不接受新任务
           // 3、rs >= shutdown && workQueue.isEmppty:--不接受新任务
           // 逻辑判断不成立
           // 1、rs==shutdown&&firstTask != null:此时不接受新任务,但是仍会执行队列中的任务
           // 2、rs==shotdown&&firstTask == null:会执行addWork(null,false)
           //  防止了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。
           //  添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务
           if (rs >= SHUTDOWN &&! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))
               return false;
           // 死循环
           // 如果线程池状态为RUNNING并且队列中还有需要执行的任务
           for (;;) {
               // 获取线程池中线程数量
               int wc = workerCountOf(c);
               // 如果超出容量或者最大线程池容量不在接受新任务
               if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                   return false;
               // 线程安全增加工作线程数
               if (compareAndIncrementWorkerCount(c))
                   // 跳出retry
                   break retry;
               c = ctl.get();  // Re-read ctl
               // 如果线程池状态发生变化,重新循环
               if (runStateOf(c) != rs)
                   continue retry;
               // else CAS failed due to workerCount change; retry inner loop
           }
       }
       // 走到这里说明工作线程数增加成功
       boolean workerStarted = false;
       boolean workerAdded = false;
       Worker w = null;
       try {
           final ReentrantLock mainLock = this.mainLock;
           w = new Worker(firstTask);
           final Thread t = w.thread;
           if (t != null) {
               // 加锁
               mainLock.lock();
               try {
                   // Recheck while holding lock.
                   // Back out on ThreadFactory failure or if
                   // shut down before lock acquired.
                   int c = ctl.get();
                   int rs = runStateOf(c);
                   // RUNNING状态 || SHUTDONW状态下清理队列中剩余的任务
                   if (rs < SHUTDOWN ||
                       (rs == SHUTDOWN && firstTask == null)) {
                       // 检查线程状态
                       if (t.isAlive()) // precheck that t is startable
                           throw new IllegalThreadStateException();
                       // 将新启动的线程添加到线程池中
                       workers.add(w);
                       // 更新线程池线程数且不超过最大值
                       int s = workers.size();
                       if (s > largestPoolSize)
                           largestPoolSize = s;
                       workerAdded = true;
                   }
               } finally {
                   mainLock.unlock();
               }
               // 启动新添加的线程,这个线程首先执行firstTask,然后不停的从队列中取任务执行
               if (workerAdded) {
                   //执行ThreadPoolExecutor的runWoker方法
                   t.start();
                   workerStarted = true;
               }
           }
       } finally {
           // 线程启动失败,则从wokers中移除w并递减wokerCount
           if (! workerStarted)
               // 递减wokerCount会触发tryTerminate方法
               addWorkerFailed(w);
       }
       return workerStarted;
   }

addWorker之后是runWorker,第一次启动会执行初始化传进来的任务firstTask;然后会从workQueue中取任务执行,如果队列为空则等待keepAliveTime这么长时间

 

final void runWorker(Worker w) {
       Thread wt = Thread.currentThread();
       Runnable task = w.firstTask;
       w.firstTask = null;
       // 允许中断
       w.unlock(); // allow interrupts
       boolean completedAbruptly = true;
       try {
           // 如果getTask返回null那么getTask中会将workerCount递减,如果异常了这个递减操作会在processWorkerExit中处理
           while (task != null || (task = getTask()) != null) {
               w.lock();
               // If pool is stopping, ensure thread is interrupted;
               // if not, ensure thread is not interrupted.  This
               // requires a recheck in second case to deal with
               // shutdownNow race while clearing interrupt
               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);
       }
   }

我们看下getTask是如何执行的

 

private Runnable getTask() {
       boolean timedOut = false; // Did the last poll() time out?
       // 死循环
       retry: for (;;) {
           // 获取线程池状态
           int c = ctl.get();
           int rs = runStateOf(c);
           // Check if queue empty only if necessary.
           // 1.rs > SHUTDOWN 所以rs至少等于STOP,这时不再处理队列中的任务
           // 2.rs = SHUTDOWN 所以rs>=STOP肯定不成立,这时还需要处理队列中的任务除非队列为空
           // 这两种情况都会返回null让runWoker退出while循环也就是当前线程结束了,所以必须要decrement
           if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
               // 递减workerCount值
               decrementWorkerCount();
               return null;
           }
           // 标记从队列中取任务时是否设置超时时间
           boolean timed; // Are workers subject to culling?
           // 1.RUNING状态
           // 2.SHUTDOWN状态,但队列中还有任务需要执行
           for (;;) {
               int wc = workerCountOf(c);
               // 1.core thread允许被超时,那么超过corePoolSize的的线程必定有超时
               // 2.allowCoreThreadTimeOut == false && wc >
               // corePoolSize时,一般都是这种情况,core thread即使空闲也不会被回收,只要超过的线程才会
               timed = allowCoreThreadTimeOut || wc > corePoolSize;
               // 从addWorker可以看到一般wc不会大于maximumPoolSize,所以更关心后面半句的情形:
               // 1. timedOut == false 第一次执行循环, 从队列中取出任务不为null方法返回 或者
               // poll出异常了重试
               // 2.timeOut == true && timed ==
               // false:看后面的代码workerQueue.poll超时时timeOut才为true,
               // 并且timed要为false,这两个条件相悖不可能同时成立(既然有超时那么timed肯定为true)
               // 所以超时不会继续执行而是return null结束线程。
               if (wc <= maximumPoolSize && !(timedOut && timed))
                   break;
               // workerCount递减,结束当前thread
               if (compareAndDecrementWorkerCount(c))
                   return null;
               c = ctl.get(); // Re-read ctl
               // 需要重新检查线程池状态,因为上述操作过程中线程池可能被SHUTDOWN
               if (runStateOf(c) != rs)
                   continue retry;
               // else CAS failed due to workerCount change; retry inner loop
           }
           try {
               // 1.以指定的超时时间从队列中取任务
               // 2.core thread没有超时
               Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
               if (r != null)
                   return r;
               timedOut = true;// 超时
           } catch (InterruptedException retry) {
               timedOut = false;// 线程被中断重试
           }
       }
   }

下面我们看下processWorkerExit是如何工作的

 

private void processWorkerExit(Worker w, boolean completedAbruptly) {
       // 正常的话再runWorker的getTask方法workerCount已经被减一了
       if (completedAbruptly)
           decrementWorkerCount();
       final ReentrantLock mainLock = this.mainLock;
       mainLock.lock();
       try {
           // 累加线程的completedTasks
           completedTaskCount += w.completedTasks;
           // 从线程池中移除超时或者出现异常的线程
           workers.remove(w);
       } finally {
           mainLock.unlock();
       }
       // 尝试停止线程池
       tryTerminate();
       int c = ctl.get();
       // runState为RUNNING或SHUTDOWN
       if (runStateLessThan(c, STOP)) {
           // 线程不是异常结束
           if (!completedAbruptly) {
               // 线程池最小空闲数,允许core thread超时就是0,否则就是corePoolSize
               int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
               // 如果min == 0但是队列不为空要保证有1个线程来执行队列中的任务
               if (min == 0 && !workQueue.isEmpty())
                   min = 1;
               // 线程池还不为空那就不用担心了
               if (workerCountOf(c) >= min)
                   return; // replacement not needed
           }
           // 1.线程异常退出
           // 2.线程池为空,但是队列中还有任务没执行,看addWoker方法对这种情况的处理
           addWorker(null, false);
       }
   }

tryTerminate

processWorkerExit方法中会尝试调用tryTerminate来终止线程池。这个方法在任何可能导致线程池终止的动作后执行:比如减少wokerCount或SHUTDOWN状态下从队列中移除任务。

 

final void tryTerminate() {
       for (;;) {
           int c = ctl.get();
           // 以下状态直接返回:
           // 1.线程池还处于RUNNING状态
           // 2.SHUTDOWN状态但是任务队列非空
           // 3.runState >= TIDYING 线程池已经停止了或在停止了
           if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))
               return;
           // 只能是以下情形会继续下面的逻辑:结束线程池。
           // 1.SHUTDOWN状态,这时不再接受新任务而且任务队列也空了
           // 2.STOP状态,当调用了shutdownNow方法
           // workerCount不为0则还不能停止线程池,而且这时线程都处于空闲等待的状态
           // 需要中断让线程“醒”过来,醒过来的线程才能继续处理shutdown的信号。
           if (workerCountOf(c) != 0) { // Eligible to terminate
               // runWoker方法中w.unlock就是为了可以被中断,getTask方法也处理了中断。
               // ONLY_ONE:这里只需要中断1个线程去处理shutdown信号就可以了。
               interruptIdleWorkers(ONLY_ONE);
               return;
           }
           final ReentrantLock mainLock = this.mainLock;
           mainLock.lock();
           try {
               // 进入TIDYING状态
               if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                   try {
                       // 子类重载:一些资源清理工作
                       terminated();
                   } finally {
                       // TERMINATED状态
                       ctl.set(ctlOf(TERMINATED, 0));
                       // 继续awaitTermination
                       termination.signalAll();
                   }
                   return;
               }
           } finally {
               mainLock.unlock();
           }
           // else retry on failed CAS
       }
   }

shutdown这个方法会将runState置为SHUTDOWN,会终止所有空闲的线程。shutdownNow方法将runState置为STOP。和shutdown方法的区别,这个方法会终止所有的线程。主要区别在于shutdown调用的是interruptIdleWorkers这个方法,而shutdownNow实际调用的是Worker类的interruptIfStarted方法:

他们的实现如下:

 

public void shutdown() {
       final ReentrantLock mainLock = this.mainLock;
       mainLock.lock();
       try {
           checkShutdownAccess();
           // 线程池状态设为SHUTDOWN,如果已经至少是这个状态那么则直接返回
           advanceRunState(SHUTDOWN);
           // 注意这里是中断所有空闲的线程:runWorker中等待的线程被中断 → 进入processWorkerExit →
           // tryTerminate方法中会保证队列中剩余的任务得到执行。
           interruptIdleWorkers();
           onShutdown(); // hook for ScheduledThreadPoolExecutor
       } finally {
           mainLock.unlock();
       }
       tryTerminate();
   }
public List<Runnable> shutdownNow() {
   List<Runnable> tasks;
   final ReentrantLock mainLock = this.mainLock;
   mainLock.lock();
   try {
       checkShutdownAccess();
       // STOP状态:不再接受新任务且不再执行队列中的任务。
       advanceRunState(STOP);
       // 中断所有线程
       interruptWorkers();
       // 返回队列中还没有被执行的任务。
       tasks = drainQueue();
   }
   finally {
       mainLock.unlock();
   }
   tryTerminate();
   return tasks;
}
private void interruptIdleWorkers(boolean onlyOne) {
   final ReentrantLock mainLock = this.mainLock;
   mainLock.lock();
   try {
       for (Worker w : workers) {
           Thread t = w.thread;
           // w.tryLock能获取到锁,说明该线程没有在运行,因为runWorker中执行任务会先lock,
           // 因此保证了中断的肯定是空闲的线程。
           if (!t.isInterrupted() && w.tryLock()) {
               try {
                   t.interrupt();
               } catch (SecurityException ignore) {
               } finally {
                   w.unlock();
               }
           }
           if (onlyOne)
               break;
       }
   }
   finally {
       mainLock.unlock();
   }
}
void interruptIfStarted() {
   Thread t;
   // 初始化时state == -1
   if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
       try {
           t.interrupt();
       } catch (SecurityException ignore) {
       }
   }
}

线程池的使用

线程池的创建

我们可以通过ThreadPoolExecutor来创建一个线程池

 

/**
    * @param corePoolSize 线程池基本大小,核心线程池大小,活动线程小于corePoolSize则直接创建,大于等于则先加到workQueue中,
    * 队列满了才创建新的线程。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,
    * 等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法,
    * 线程池会提前创建并启动所有基本线程。
    * @param maximumPoolSize 最大线程数,超过就reject;线程池允许创建的最大线程数。如果队列满了,
    * 并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务
    * @param keepAliveTime
    * 线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率
    * @param unit  线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、
    * 毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)
    * @param workQueue 工作队列,线程池中的工作线程都是从这个工作队列源源不断的获取任务进行执行
    */
   public ThreadPoolExecutor(int corePoolSize,
              int maximumPoolSize,
              long keepAliveTime,
              TimeUnit unit,
              BlockingQueue<Runnable> workQueue) {
       // threadFactory用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
               Executors.defaultThreadFactory(), defaultHandler);
   }

向线程池提交任务

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。通过以下代码可知execute()方法输入的任务是一个Runnable类的实例。

 

threadsPool.execute(new Runnable() {
       @Override
       public void run() {
       }
   });

submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

 

Future<Object> future = executor.submit(harReturnValuetask);
 try
   {
       Object s = future.get();
   }catch(
   InterruptedException e)
   {
       // 处理中断异常
   }catch(
   ExecutionException e)
   {
       // 处理无法执行任务异常
   }finally
   {
       // 关闭线程池
       executor.shutdown();
   }

关闭线程池

可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。

合理的配置线程池

要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。

1、任务的性质:CPU密集型任务、IO密集型任务和混合型任务。

2、任务的优先级:高、中和低。

3、任务的执行时间:长、中和短。

4、任务的依赖性:是否依赖其他系统资源,如数据库连接。

性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行

如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU。

建议使用有界队列。有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点儿,比如几千。有时候我们系统里后台任务线程池的队列和线程池全满了,不断抛出抛弃任务的异常,通过排查发现是数据库出现了问题,导致执行SQL变得非常缓慢,因为后台任务线程池里的任务全是需要向数据库查询和插入数据的,所以导致线程池里的工作线程全部阻塞,任务积压在线程池里。如果当时我们设置成无界队列,那么线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题。当然,我们的系统所有的任务是用单独的服务器部署的,我们使用不同规模的线程池完成不同类型的任务,但是出现这样问题时也会影响到其他任务。

线程池的监控

如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性

  • taskCount:线程池需要执行的任务数量。

  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。

  • largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。

  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。

  • getActiveCount:获取活动的线程数。

通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。例如,监控任务的平均执行时间、最大执行时间和最小执行时间等。

来源:https://blog.csdn.net/fuyuwei2015/article/details/72758179

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值