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 多線程開發優化有兩個思路:
- 針對鎖的優化
- 線程池優化