首頁 > 軟體

非常適合新手學生的Java執行緒池超詳細分析

2022-03-21 16:01:52

執行緒池的好處

  • 可以實現執行緒的複用,避免重新建立執行緒和銷燬執行緒。建立執行緒和銷燬執行緒對CPU的開銷是很大的。
  • 可以限制最大可建立的執行緒數,可根據自己的機器效能動態調整執行緒池引數,提高應用效能。
  • 提供定時執行、並行數控制等功能。
  • 統一管理執行緒。

建立執行緒池的五種方式

1:快取執行緒池(不推薦)

2:固定容量執行緒池(不推薦)

3:單個執行緒池(不推薦)

4:定時任務執行緒池(不推薦)

5:通過ThreadPoolExecutor構造方法建立執行緒池(阿里巴巴開發手冊十分推薦)

前面4種建立執行緒池的方式都是通過Executors的靜態方法來建立。

快取執行緒池CachedThreadPool

	ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int finalI = i;
            executorService.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
                }
            });
        }

為什麼不推薦使用快取執行緒池?

原始碼分析

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
    }
 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
    }

通過上面兩個程式碼片段,我們可以看出CachedThreadPool的maximumPoolSize為Integer的最大值2147483647,相當於可以無限的建立執行緒,而建立執行緒是需要記憶體的,這樣就會造成記憶體溢位,而且一般的機器也沒用那麼大的記憶體給它建立這麼大量的執行緒。

固定容量執行緒池FixedThreadPool

newFixedThreadPool(int num),num就是我們要指定的固定執行緒數量

	ExecutorService executorService = Executors.newFixedThreadPool(5);

      for (int i = 0; i < 10; i++) {
          final int finalI = i;
          executorService.execute(new Runnable() {
              public void run() {
                  System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
              }
          });
      }

輸出:

pool-1-thread-5<thread->run>4
pool-1-thread-4<thread->run>3
pool-1-thread-5<thread->run>5
pool-1-thread-3<thread->run>2
pool-1-thread-3<thread->run>8
pool-1-thread-3<thread->run>9
pool-1-thread-2<thread->run>1
pool-1-thread-1<thread->run>0
pool-1-thread-5<thread->run>7
pool-1-thread-4<thread->run>6

可以看出起到了執行緒的複用。

為什麼FixedThreadPool是固定執行緒池?

原始碼分析

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
    }

通過這個原始碼可以看出,核心執行緒數(corePoolSize)和最大執行緒數(maximumPoolSize)都為nThreads,因為只有這樣,執行緒池才不會進行擴容,執行緒數才固定。

單個執行緒池SingleThreadExecutor

	ExecutorService executorService = Executors.newSingleThreadExecutor();

      for (int i = 0; i < 10; i++) {
          final int finalI = i;
          executorService.execute(new Runnable() {
              public void run() {
                  System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
              }
          });

      }

為什麼SingleThreadExecutor只含有一個執行緒?

原始碼分析

public static ExecutorService newSingleThreadExecutor() {
        return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
    }

通過這個原始碼可以看出,核心執行緒數(corePoolSize)和最大執行緒數(maximumPoolSize)都為1,所以它只含有一個執行緒。

定時任務執行緒池ScheduledThreadPool

	  int initDelay=10; //初始化延時
      int period=1;//初始化延遲過了之後,每秒的延時

      ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

      scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName()+"<thread->run>");
          }
      },initDelay,period, TimeUnit.SECONDS);

這段程式碼的效果是:程式執行之後等10秒,然後輸出第一次結果,之後每隔1秒輸出一次結果。

為什麼不推薦使用ScheduledThreadPool?

原始碼分析

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, 2147483647, 10L, TimeUnit.MILLISECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue());
    }

可以看出ScheduledThreadPool的最大執行緒數(maximumPoolSize)為Integer的最大值2147483647,相當於可以無限的建立執行緒,而建立執行緒是需要記憶體的,這樣就會造成記憶體溢位,而且一般的機器也沒用那麼大的記憶體給它建立這麼大量的執行緒。

ThreadPoolExecutor建立執行緒池(十分推薦)

	ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
              2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
              Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

      for (int i = 0; i < 12; i++) {
          final int finalI = i;
          threadPoolExecutor.execute(new Runnable() {
              public void run() {
                  System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
              }
          });
      }

ThreadPoolExecutor的七個引數詳解

	public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        
    }
  • corePoolSize:核心執行緒數。這些執行緒一旦被建立不會被銷燬,是一直存在的。執行緒池預設是沒有執行緒的,當有任務到來了,就會通過ThreadFactory去建立執行緒,並一直存在。
  • maximumPoolSize:最大執行緒數。非核心執行緒數=maximumPoolSize-corePoolSize,非核心執行緒數其實就是可擴容的執行緒數,可能會被銷燬。
  • keepAliveTime:非核心執行緒的空閒存活時間。當通過擴容生成的非核心執行緒數在keepAliveTime這個時間後還處於空閒狀態,則會銷燬這些非核心執行緒。
  • unit:keepAliveTime的時間單位,例如:秒
  • workQueue:等待區。當來了>corePoolSize的任務時會把任務存放在workQueue這個阻塞佇列中,等待其他執行緒處理。
  • threadFactory:執行緒工廠。建立執行緒的一種方式。
  • handler:拒絕策略。當來了>最大執行緒數+workQueue的容量則會執行拒絕策略

workQueue

ArrayBlockingQueue:有界阻塞佇列。佇列有大小限制,當容量超過時則會觸發擴容或者拒絕策略。

	public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

LinkedBlockingQueue:無界阻塞佇列,佇列無大小限制,可能會造成記憶體溢位。

	 public LinkedBlockingQueue() {
        this(2147483647);
    }

handler

AbortPolicy:直接拋異常

	public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() {
        }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
        }
    }

DiscardPolicy:不作任何操作。默默丟棄任務

	public static class DiscardPolicy implements RejectedExecutionHandler {
        public DiscardPolicy() {
        }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

DiscardOldestPolicy:丟掉存在時間最長的任務

	public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public DiscardOldestPolicy() {
        }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }

        }
    }

CallerRunsPolicy:讓提交任務的執行緒去處理任務

	public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() {
        }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }

        }
    }

threadFactory

	ThreadFactory threadFactory = Executors.defaultThreadFactory();

      threadFactory.newThread(new Runnable() {
          @Override
          public void run() {
              System.out.println("threadFactory");
          }
      }).start();

如何觸發拒絕策略和執行緒池擴容?

	ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
              2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
              Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

      for (int i = 0; i < 26; i++) { //並行數26
          final int finalI = i;
          threadPoolExecutor.execute(new Runnable() {
              public void run() {
                  System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
              }
          });
      }
      /**
       * 核心執行緒數=10,最大執行緒數=20,故可擴容執行緒數=20-10
       * BlockingQueue的大小為5,故等待區的大小為5,也就是當並行數<=核心執行緒數+5不會擴容,並行數大於16才會擴容
       *
       * 觸發擴容:並行數>核心執行緒數+阻塞佇列的大小
       * 對於這段程式碼,如果來了26個並行,10個並行會被核心執行緒處理,5個會在等待區,剩下11個會因為等待區滿了而觸發擴容
       * 因為這裡最多能夠擴容10個,這裡卻是11個,所以會觸發拒絕策略
       */
  • 為什麼這段程式碼會觸發拒絕策略

對於這段程式碼,如果來了26個並行,10個並行會被核心執行緒處理,5個會在等待區,剩下11個會因為等待區滿了而觸發擴容,但是又因為因為這裡最多能夠擴容10個,這裡卻是11個,所以會觸發拒絕策略。

  • 怎麼觸發擴容

觸發擴容:並行數>核心執行緒數(corePoolSize)+阻塞佇列(workQueue)的大小

  • 使用Java純手寫一個執行緒池

下期文章連結https://www.jb51.net/article/241589.htm

到此這篇關於非常適合新手學生的Java執行緒池超詳細分析的文章就介紹到這了,更多相關Java 執行緒池內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com