Java多線程併發編程,多線程優化-線程池的常用創建方式及使用總結

1、簡單的線程池創建Executors

1.1 newCachedThreadPool()

public static ExecutorService newCachedThreadPool()

說明:創建一個線程池。需要使用線程時從線程池中獲取線程,如果無可用線程,則創建一個線程,在使用使用後放入線程池。線程池中60秒未使用的線程將被終止並從緩存中移除。

因此通過該方法創建的線程池,長時間不適用,將不怎麼消耗資源。

該線程池適合用於執行短暫異步任務

代碼示例:

package com.liyong.reactor.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleNewCachedThreadPool {
	
	static class Worker implements Runnable {
		@Override
		public void run() {
			try {
				Thread.sleep(30);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + " exe success");
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		ExecutorService executor = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			Thread.sleep(10);
			executor.submit(new Worker());
		}
		executor.shutdown();
	}
}

 運行結果:

pool-1-thread-1 exe success
pool-1-thread-2 exe success
pool-1-thread-3 exe success
pool-1-thread-4 exe success
pool-1-thread-1 exe success
pool-1-thread-2 exe success
pool-1-thread-3 exe success
pool-1-thread-4 exe success
pool-1-thread-1 exe success
pool-1-thread-2 exe success

說明:每個任務執行30ms,每10ms提交一個任務,所以部分線程得已複用。

1.2 newFixedThreadPool(int nThreads)

public static ExecutorService newFixedThreadPool(int nThreads)

 說明:該方法創建一個固定線程數量、無界等待隊列的線程池,在任何時候最多隻有nThread個線程在處理任務。當所有線程都被佔用時,任務將放到等待隊列中,等待隊列無限大。池中的線程永久有效,直到明確的關閉線程池爲止。

該線程池可用於可控的任務提交和任務執行,否則在大量任務堆積時,會造成OOM,而且已經提交的任務就丟失了。

代碼實例:

package com.liyong.reactor.thread;

import java.time.LocalTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleNewFixedThreadPool {
	static class Worker implements Runnable {

		private final int index;

		public Worker(int index) {
			this.index = index;
		}

		@Override
		public void run() {
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(LocalTime.now() + " index:" + index + "  " + Thread.currentThread().getName() + " exe success");
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ExecutorService executor = Executors.newFixedThreadPool(4);
		for (int i = 0; i < 10; i++) {
			Thread.sleep(10);
			System.out.println(LocalTime.now() + " thread index: " + (i + 1) + " submit success");
			executor.submit(new Worker(i + 1));
		}
		executor.shutdown();
	}
}

 運行結果:

16:54:58.823 thread index: 1 submit success
16:54:58.835 thread index: 2 submit success
16:54:58.845 thread index: 3 submit success
16:54:58.855 thread index: 4 submit success
16:54:58.865 thread index: 5 submit success
16:54:58.875 thread index: 6 submit success
16:54:58.885 thread index: 7 submit success
16:54:58.895 thread index: 8 submit success
16:54:58.905 thread index: 9 submit success
16:54:58.915 thread index: 10 submit success
16:54:59.125 index:1  pool-1-thread-1 exe success
16:54:59.135 index:2  pool-1-thread-2 exe success
16:54:59.145 index:3  pool-1-thread-3 exe success
16:54:59.155 index:4  pool-1-thread-4 exe success
16:54:59.425 index:5  pool-1-thread-1 exe success
16:54:59.435 index:6  pool-1-thread-2 exe success
16:54:59.445 index:7  pool-1-thread-3 exe success
16:54:59.455 index:8  pool-1-thread-4 exe success
16:54:59.725 index:9  pool-1-thread-1 exe success
16:54:59.735 index:10  pool-1-thread-2 exe success

說明:設置了固定的線程池大小爲4,每個線程執行300ms,每隔10ms提交一個任務到線程池中。

從輸出結果 ,可以看到有且僅有4個線程能執行任務,剩下的任務都在進行排隊。比如 index爲10的任務,16:54:58.915提交任務,16:54:59.735完成任務,總共耗時820ms,執行時間爲300ms,在隊列中等待了520ms的時間。

1.3 newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor()

說明:創建一個單線程,無界隊列的線程池。任務保證順序執行,隨時都有且僅有一個線程在執行任務。

適合場景:適合可控任務數量,可控執行時間的任務使用,並且提供了任務的順序執行。

代碼示例:

package com.liyong.reactor.thread;

import java.time.LocalTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleNewSingleThreadPool {
	static class Worker implements Runnable {

		private final int index;

		public Worker(int index) {
			this.index = index;
		}

		@Override
		public void run() {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(LocalTime.now() + " index:" + index + "  " + Thread.currentThread().getName() + " exe success");
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 5; i++) {
			Thread.sleep(10);
			System.out.println(LocalTime.now() + " thread index: " + (i + 1) + " submit success");
			executor.submit(new Worker(i + 1));
		}
		executor.shutdown();
	}
}

 執行結果:

17:05:52.656 thread index: 1 submit success
17:05:52.667 thread index: 2 submit success
17:05:52.677 thread index: 3 submit success
17:05:52.687 thread index: 4 submit success
17:05:52.697 thread index: 5 submit success
17:05:52.757 index:1  pool-1-thread-1 exe success
17:05:52.857 index:2  pool-1-thread-1 exe success
17:05:52.957 index:3  pool-1-thread-1 exe success
17:05:53.057 index:4  pool-1-thread-1 exe success
17:05:53.157 index:5  pool-1-thread-1 exe success

說明: 任務執行耗時100ms,沒10ms提交一個任務,總共提交5個任務,由結果的index序號觀察,任務是順序執行的,並且僅有一個線程在處理任務,其他未處理的任務再排隊等待。index=5的任務,從提交任務到處理完成,總耗時460ms,等待了360ms的時間

1.4、newScheduledThreadPool(int corePoolSize)

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

 說明:創建一個線程池,可以指定線程數,排隊隊列無界,可以調度命令在給定的延遲之後運行,或定期執行。

代碼示例:

package com.liyong.reactor.thread;

import java.io.IOException;
import java.time.LocalTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class SimpleNewScheduledThreadPool {
	static class Worker implements Runnable {

		private final int index;

		public Worker(int index) {
			this.index = index;
		}

		@Override
		public void run() {
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(LocalTime.now() + " index:" + index + "  " + Thread.currentThread().getName() + " exe success");
		}
	}

	public static void main(String[] args) throws InterruptedException, IOException {
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
		
		System.out.println(LocalTime.now() + " thread index: " + 1111 + " submit success");
		executor.schedule(new Worker(1111), 5, TimeUnit.SECONDS);
		
		System.out.println(LocalTime.now() + " thread index: " + 2222 + " submit success");
		executor.scheduleWithFixedDelay(new Worker(2222), 6000, 200, TimeUnit.MILLISECONDS);
		
//		System.out.println(LocalTime.now() + " thread index: " + 3333 + " submit success");
//		executor.scheduleAtFixedRate(new Worker(3333), 6000, 200, TimeUnit.MILLISECONDS);
		
		System.in.read();
		executor.shutdown();
	}
}

 輸出結果:

17:37:23.877 thread index: 1111 submit success
17:37:23.878 thread index: 2222 submit success
17:37:29.179 index:1111  pool-1-thread-1 exe success
17:37:30.179 index:2222  pool-1-thread-2 exe success
17:37:30.680 index:2222  pool-1-thread-1 exe success
17:37:31.181 index:2222  pool-1-thread-2 exe success
17:37:31.682 index:2222  pool-1-thread-3 exe success
17:37:32.183 index:2222  pool-1-thread-1 exe success
17:37:32.684 index:2222  pool-1-thread-4 exe success
17:37:33.185 index:2222  pool-1-thread-2 exe success
17:37:33.686 index:2222  pool-1-thread-2 exe success

說明: 任務執行300ms,index 1111的任務5s後開始執行;index 2222的任務6000ms後開始執行,延時200ms後再次執行;index 3333的任務6000ms後開始執行,沒間隔200ms後開始執行,間隔是以該任務上次執行結束時開始計算。

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);

指定在多久後開始執行,僅執行一次

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

指定多久後開始執行,每個delay的時長後,再次執行,從輸出日誌可以看到,該調度器在存在足夠的線程數的情況下,該任務會存在多個線程同時在執行。

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

指定多久後開始執行,每個執行週期爲period,period是從該任務上次結束後開始統計,所以即使線程數足夠的清康熙,該任務只有一個線程在執行

1.5  newSingleThreadScheduledExecutor() 

public static ScheduledExecutorService newSingleThreadScheduledExecutor()

說明:該方法與1.4中的Executors.newScheduledThreadPool(1)效果一樣

2、直接使用ThreadPoolExecutor類創建線程池,不使用Executors創建線程池

package com.liyong.reactor.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CustomThreadPoolExecutor {
	public static void main(String[] args) {
		// 丟棄老的任務
		RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
		// 將拋出RejectedExecutionException異常
		new ThreadPoolExecutor.AbortPolicy();
		// 調用者自己執行任務
		new ThreadPoolExecutor.CallerRunsPolicy();
		// 丟棄任務
		new ThreadPoolExecutor.DiscardPolicy();
		/*
		 * int corePoolSize 核心線程數
		 * int maximumPoolSize 池中允許的最大線程數 
		 * long keepAliveTime 當線程數大於核心時,這個空閒線程將等待多長時間閒置後被回收
		 * TimeUnit unit 閒置時間單位
		 * BlockingQueue<Runnable> workQueue 任務隊列,主要用於設置任務隊列深度 
		 * RejectedExecutionHandler handler 任務拒絕策略
		 */
		new ThreadPoolExecutor(8, 64, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), handler);
	}
}

說明: 使用Executors創建的線程池,除了調度線程池外,其他線程池底層都是通過ThreadPoolExecutor來進行構造的。通過自己構造線程池,可以對線程池的各個細節進行設置。

關於拒絕策略:java提供了4中拒絕策略

        // 丟棄老的任務
        new ThreadPoolExecutor.DiscardOldestPolicy();
        // 將拋出RejectedExecutionException異常
        new ThreadPoolExecutor.AbortPolicy();
        // 調用者自己執行任務
        new ThreadPoolExecutor.CallerRunsPolicy();
        // 丟棄任務
        new ThreadPoolExecutor.DiscardPolicy();

但是這4中拒絕策略在我們真正的業務中,其實實用性不是太高。往往可以通過自定義進行優化

優化方案:

從上面的線程池配置中,線程數的配置其實是比較關鍵的,它直接影響了我們程序運行的效率。那怎麼設置一個合適的線程池大小呢?

將被處理的任務分爲: CPU密集型的、IO密集型的

如果是CPU密集型的任務,那麼就是需要“長時間”佔用CPU進行運算的,線程數儘量小,比如配置:CPU 個數 +1 的線程數。

如果是IO密集型的任務,因爲IO操作時是不暫用CPU的,儘量不要讓CPU閒下來,應儘可能大的增加線程數,比如配置: CPU 個數 * 2 +1。

Runtime.getRuntime().availableProcessors()

可以通過該方法獲取當前機器的可用處理器個數。

如果任務對其他資源有依賴,比如從數據庫查詢數據,從遠處服務器拉取數據。等待數據資源時間越長,CPU空閒的時間就越長,所以增加線程數是更好利用CPU的好方法。

因此給出一個估算的公式:

最佳線程數目 = (( 線程等待時間 + 線程 CPU 時間 ) / 線程 CPU 時間 ) * CPU 數目

 將公式進一步化簡,得到:

最佳線程數目 = ( 線程等待時間與線程CPU 時間之比+1 ) * CPU 數目

因此得到結論:線程等待時間所佔比例越高,需要越多線程。線程 CPU 時間所佔比例越高,需要越少線程。 

自定義決絕策略:

package com.liyong.reactor.thread;

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {

	@Override
	public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
		//TODO  寫日誌
		//TODO  發MQ消息
		//TODO  發管理員提醒
		//TODO  executor.getQueue().put(r);  
	}

}

說明:此處只是一段僞代碼,主要爲了說明在自定義拒絕策略時,可以向系統管理員,以及相關負責人發出警告,這樣可以快速進行人工干預,比如:加機器、代碼BUG定位等相關操作。

總結:

Java 多線程開發優化有兩個思路:

  • 針對鎖的優化
  • 線程池優化
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章