Java多线程 - 线程池 ThreadPoolExecutor类的使用

  • ThreadPoolExecutor类可以非常方便的创建线程池对象,而不需要程序员设计大量的new去实例化Thread相关的代码

    • 最常用的构造方法 (不过threadFactory可以视情况设或不设)

      ThreadPoolExecutor(
          int corePoolSize,
          int maximumPoolSize,
          long keepAliveTime,
          TimeUnit unit,
          BlockingQueue<Runnable> workQueue,
          ThreadFactory threadFactory
      )
    • 参数解释如下

      • corePoolSize

        • 核心线程数,默认情况下核心线程会一直存活,即使处于閒置状态也不会受存keepAliveTime限制 (除非将allowCoreThreadTimeOut设置爲true)

      • maximumPoolSize

        • 线程池所能容纳的最大线程数,超过这个数的线程将被阻塞

        • 但是当任务队列workQueue是一个没有设置大小的LinkedBlockingDeque时,这个值无效

      • keepAliveTime

        • 非核心线程(也就是线程数量大于corePoolSize的线程) 的閒置超时时间,超过这个时间就会被回收

      • unit : keepAliveTime参数的时间单位,像是TimeUnit.SECONDS、TimeUnit.MILLISECONDS..

      • workQueue

        • 执行前用于保持任务的队列,此队列仅保持由pool.execute(Runnable r)方法提交的Runnable任务

        • 常用的有三种队列,分别是LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue

      • threadFactory : 线程工厂,可以自定义创建新线程时想添加的功能,像是为每个Thread取名之类的

        • ThreadFactory是一个接口,只有一个方法Thread newThread(Runnable r)

        • 当程序员调用pool.execute(Runnable r)时,此r就会经过newThread()方法,去生成一个Thread

    • 线程池运行的规则

      • 线程池的线程执行规则跟任务队列使用哪种BlockingQueue有很大的关系,分为两种不同的BlockingQueue的情况来讨论

        • 以下假设 线程数量 代表pool.execute(Runnable r)总共需要执行的Runnable任务数量,也就是程序员设置的要执行的任务们的数量

      1. 使用LinkedBlockingQueue

        • 假设 线程数量 <= 核心线程数量

          • 那麽直接创建线程去运行这些任务,不会放入队列中,有几个任务就创几条,反正再怎麽创也不会超过核心线程数量就好

        • 假设 线程数量 > 核心线程数量

          • 任务队列 是 LinkedBlockingDeque,且 设置大小限制

            • 先将超过核心线程的任务放在LinkedBlockingDeque中排队,但是如果LinkedBlockingDeque也给塞满时,多出来的任务会直接创建新线程来执行

            • 而当多出来的任务太多,假设使得 总线程数量 > 最大线程数时,就会抛出异常,如果没有超过最大线程数,那就没事

            • 要注意,这些新创建的这些线程属于非核心线程,在任务完成后,当閒置时间达到了超时时间就会被清除

          • 任务队列 是 LinkedBlockingDeque,且 没有 大小限制

            • 也是将超过核心线程的任务放在LinkedBlockingDeque中排队

            • 但因为没有设置大小限制,所以LinkedBlockingDeque永远不会塞满,所以永远不会有多出来的任务,所以也就不会去创建核心线程之外新的线程,所以他的总线程数量会维持在核心线程数量corePoolSize

            • 也就是说当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的,因为他的线程数最多不会超过核心线程数

      2. 使用SynchronousQueue

        • 假设 线程数量 <= 核心线程数量

          • 一样是直接创建几条线程去运行这些任务,不会放入队列中

        • 假设 线程数量 > 核心线程数量 且 <= 最大线程数

          • 线程池会创建新线程执行任务,这些任务也不会被放在SynchronousQueue中

          • 这些新创建的线程属于非核心线程,在任务完成后,閒置时间达到了超时时间就会被清除

        • 假设 线程数量 > 核心线程数量 且 > 最大线程数

          • 会因爲线程池拒绝添加任务而抛出异常

          • 会报异常是因为SynchronousQueue没有数量限制,不如说他根本不保持这些任务,而是直接交给线程池去执行,所以当任务数量超过最大线程数时才会直接抛异常

    • 具体实例

      public class Main {
          public static void main(String[] args) {
              ExecutorService threadPool = new ThreadPoolExecutor(
                  5,
                  10,
                  60,
                  TimeUnit.SECONDS,
                  new LinkedBlockingQueue(1),
                  new NameThreadFactory("mytestPool")
              );
              
              for(int i=1; i<=7; i++) {
                  threadPool.execute(new Runnable() {
                      @Override
                      public void run() {
                          try {
                              Thread.sleep(2000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  });
              }
              
              //取得当初设置的corePoolSize参数
              System.out.println("corePoolSize: " + ((ThreadPoolExecutor) threadPool).getCorePoolSize());
              //取得目前线程池裡总共有几条线程
              System.out.println("currentPoolSize: " + ((ThreadPoolExecutor) threadPool).getPoolSize());
              
          }
      }
      ​
      class ThreadFactory implements ThreadFactory {
          private final AtomicInteger mThreadNum;
          private final String mPrefix;
          private final boolean mDaemo;
          private final ThreadGroup mGroup;
      ​
          public NamedThreadFactory(String prefix) {
              this(prefix, true);
          }
      ​
          public NamedThreadFactory(String prefix, boolean daemo) {
              this.mThreadNum = new AtomicInteger(1);
              this.mPrefix = prefix + "-thread-";
              this.mDaemo = daemo;
              SecurityManager s = System.getSecurityManager();
              this.mGroup = s == null ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
          }
      ​
          public Thread newThread(Runnable runnable) {
              String name = this.mPrefix + this.mThreadNum.getAndIncrement();
              Thread ret = new Thread(this.mGroup, runnable, name, 0L);
              ret.setDaemon(this.mDaemo);
              return ret;
          }
      ​
          public ThreadGroup getThreadGroup() {
              return this.mGroup;
          }
      }
      mytestPool-thread-1
      mytestPool-thread-2
      mytestPool-thread-4
      mytestPool-thread-5
      mytestPool-thread-3
      mytestPool-thread-6
      corePoolSize: 5
      currentPoolSize: 6
      //...过了2000ms之后,此时虽然所有Runnable的run都运作完了,但线程池仍会继续运作,程式不会停止
      //除非使用shutdown()或shutdownNow()来停止线程池,才会使整个程式关闭
  • ThreadPoolExecutor提供的实例方法

    • execute(Runnable r) : 新增一个Runnable任务到线程池中

      • 至于此Runnable会马上执行,或是被放到队列中等待,要看此线程池的机制和当下的状况

    • shutdown() : 使当前未执行完的线程继续执行,但不再添加新的任务Task,当所有任务都运行完毕,线程池就会停止

      • 所有任务指的是 "当前正在运行的任务 + 队列中等的任务",要这两个都运行完,才算运行完毕

      • 如果main不调用shutdown()方法,就直接结束的话,那麽线程池会一直继续保持运作,以便随时执行被添加的任务,前面的例子就是因为这样,所以程序才不会停止

      • 当线程池调用shutdown()方法时,线程池的状态会立刻变成 SHUTDOWN 状态,此时不能再往线程池中添加任何任务,否则会抛出RejectedExecutrionException异常,但此时线程池不会立刻退出,他会等到池中所有任务(正在运行的+队列中的)执行完毕之后,才会退出

    • List<Runnable> shutdownNow() : 发出一个interrupt()中断池子内的所有线程,且未执行的线程不再执行,也就是说从队列中删除

      • 返回的Runnable list就是没被执行的那些Runnable们

      • 不同于上面的shutdown()方法,当线程池调用shutdownNow()方法时,线程池的状态会立刻变成 STOP 状态,并发出interrupt()试图停止所有正在执行的线程,同时他也不再处理那些还在池队列中等待的任务,取而代之的是他会将这些任务返回

        • shundown()方法会把队列中的任务跑完,但shutdownNow()不会

      • 简单的说,shutdown()就是不淮再有新任务进来了,但是已经进来的任务我会乖乖把你做完,而shutdownNow()则是不管任何事,我现在就要立马结束,没完成的任务赶紧回家吧

    • isShutdown() : 判断线程池是否处在shutdown状态

      • 他其实就是去看线程池的状态,只要状态是 SHUTDOWN 或是 STOP,就返回true

    • isTerminating() : 如果正在执行的线程池已经调用过 shutdown() 或 shutdownNow(),处在正在中止但还没完全结束的过程中,调用此方法返回true

    • isTerminated() : 如果线程池关闭后,此方法返回true

    • 小结

      • shutdown()shutdownNow()的功能是发出一个关闭大门的命令,而isShutdown()是判断这个关闭大门的命令发出或是尚未发出,isTerminating()代表大门是否正在关闭进行中,isTerminated()判断大门是否已经关闭关好了

  • 具体实例

    • 使用shutdown()来关闭线程池

      • 如果Runnable们在sleep过1秒之后,都执行完毕的话,那就能够顺利关掉线程池,程式因此停止

      class MyRunnable implements Runnable {
          @Override
          public void run() {
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("run 执行完毕");
          }
      }
      ​
      public class Main {
          public static void main(String[] args) {
              ExecutorService threadPool = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue(1));
      ​
              threadPool.execute(new MyRunnable());
              threadPool.execute(new MyRunnable());
      ​
              threadPool.shutdown();
              System.out.println("shutdown() 线程池");
          }
      }
      shutdown() 线程池
      run 执行完毕
      run 执行完毕
      //程序停止了
    • 但是如果Runnable始终不运行完的话,那麽这个被shutdown()的线程池只能一直等他,直到等到有一天他终于执行完毕之后,此线程池才会结束,而程序也才会跟著结束

      • 在此例中,两个线程都在等待x.signal(),所以他们一直处于阻塞状态,始终结束不了,因而线程池也结束不了

      class MyRunnable implements Runnable {
          private final String x = new String();
          @Override
          public void run() {
              synchronized (x){
                  try {
                      x.wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
              System.out.println("run 执行完毕");
          }
      }
      ​
      public class Main {
          public static void main(String[] args) {
              ExecutorService threadPool = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue(1));
      ​
              threadPool.execute(new MyRunnable());
              threadPool.execute(new MyRunnable());
      ​
              //使用shutdown()关闭线程池
              threadPool.shutdown();
              System.out.println("shutdown() 线程池");
          }
      }
      shutdown() 线程池
      //程序仍在运行...
    • 如果改使用shutdownNow()来结束线程池,那麽因为他会抛出一个InterruptException,所以可以解决因为wait()sleep()join()这种被阻塞的线程的等待

      • 两个线程在x.wait()处等待,线程进入阻塞状态,而当main线程执行shutdownNow()时,线程池会对他池子裡面的所有的线程发出一个interrupt(),因此他们这两个runnable线程就会抛出InterruptException之后继续往下执行,最后就执行完毕了

      • 当线程池发现所有的Runnable都执行完毕后,就可以顺利关闭线程池,结束程序

      class MyRunnable implements Runnable {
          private final String x = new String();
          @Override
          public void run() {
              synchronized (x){
                  try {
                      x.wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
              System.out.println("run 执行完毕");
          }
      }
      ​
      public class Main {
          public static void main(String[] args) {
              ExecutorService threadPool = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue(1));
      ​
              threadPool.execute(new MyRunnable());
              threadPool.execute(new MyRunnable());
      ​
              //使用shutdownNow()关闭线程池
              List<Runnable> list = threadPool.shutdownNow();
              System.out.println("shutdownNow() 线程池");
          }
      }
      shutdown() 线程池
      run 执行完毕
      run 执行完毕
      java.lang.InterruptedException
          at java.lang.Object.wait(Native Method)
          at java.lang.Object.wait(Object.java:502)
          at com.mytest.thread.MyRunnable.run(Main.java:31)
      java.lang.InterruptedException
          at java.lang.Object.wait(Native Method)
          at java.lang.Object.wait(Object.java:502)
          at com.mytest.thread.MyRunnable.run(Main.java:31)
      //程序停止
    • 但是因为shutdownNow()只能抛出一个InterruptException去解决被阻塞的线程的等待,所以假设Runnable是自愿一直运行的话,那也没办法停止此线程

      • 在此例中,Runnable一直在while loop中运行,所以被shutdownNow()的线程池即使发出了InterruptException,也没办法停止这两个线程

      • 实际上,在java中也没有任何手段可以停止他们,只能透过手动强制关闭程式来解决,所以在写多线程时,要小心避免写出这种无限循环

      class MyRunnable implements Runnable {
          @Override
          public void run() {
              int i = 0;
              while (i == 0) {
                  //do nothing
              }
              System.out.println("run 执行完毕");
          }
      }
      ​
      public class Main {
          public static void main(String[] args) {
              ExecutorService threadPool = new ThreadPoolExecutor(1, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue(1));
      ​
              threadPool.execute(new MyRunnable());
              threadPool.execute(new MyRunnable());
              
              //使用shutdownNow()关闭线程池
              threadPool.shutdownNow();
              System.out.println("shutdownNow() 线程池");
          }
      }
      shutdownNow() 线程池
      //线程仍在运行...

猜你喜欢

转载自blog.csdn.net/weixin_40341116/article/details/81367929