1.非同步程式設計
同步程式設計:執行緒等待返回
非同步程式設計:
2.顯式使用執行緒和執行緒池實現
2.1 顯式使用
- 1.實現Runnable介面
public class SyncExample {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
// 第一種
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
task1();
} catch (Exception e) {
e.printStackTrace();
}
}
});
// Java8寫法
// Thread thread = new Thread( () -> {
// try {
// task1();
// } catch (Exception e) {
// e.printStackTrace();
// }
// }, "SyncExample");
task2();
// 同步等待1任務結束
thread.join();
System.out.println(System.currentTimeMillis() - start);
}
public static void task1(){
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task1");
}
public static void task2() {
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task2");
}
}
- 2.實現Thread類重寫run方法
public class SyncExample2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
Thread thread = new Thread("Example"){
@Override
public void run() {
try {
task1();
} catch (Exception e) {
e.printStackTrace();
}
}
};
task2();
// 同步等待1任務結束
thread.join();
System.out.println(System.currentTimeMillis() - start);
}
public static void task1(){
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task1");
}
public static void task2() {
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task2");
}
}
問題:
- 每當執行非同步任務時,直接建立一個Thread,執行緒建立與銷燬有開銷,並且沒有限制執行緒個數,應用執行緒池
- Thread執行非同步任務沒有返回值,需要用Future
- 每次都建立是指令式程式設計方式,需要宣告式程式設計方法,即告訴程式我們要非同步執行,但如何實現應該對我們透明
2.2 執行緒池實現
- 無返回結果
public class SyncExample3ThreadPool {
/**
* 執行緒池核心執行緒個數為當前CPU核數
*/
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
/**
* 最大執行緒個數為CPU核數2倍
* 拒絕策略為CallerRunsPolicy,當執行緒池任務飽和,執行拒絕策略不會丟棄新任務,使用呼叫執行緒執行
* 使用命名的執行緒建立工廠來追溯業務
*/
public static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
POOL_EXECUTOR.execute( () -> {
try {
task1();
} catch (Exception e) {
e.printStackTrace();
}
});
task2();
System.out.println(System.currentTimeMillis() - start);
// 掛起當前執行緒
Thread.currentThread().join();
}
}
- 帶返回值
使用Callable型別任務提交到執行緒,返回Future物件,以阻塞的方式獲取結果
public class SyncExample3Return {
/**
* 執行緒池核心執行緒個數為當前CPU核數
*/
private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
/**
* 最大執行緒個數為CPU核數2倍
* 拒絕策略為CallerRunsPolicy,當執行緒池任務飽和,執行拒絕策略不會丟棄新任務,使用呼叫執行緒執行
* 使用命名的執行緒建立工廠來追溯業務
*/
public static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(5), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws Exception {
// 必須阻塞執行緒才能獲取結果
Future<?> taskReturn = POOL_EXECUTOR.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return task1();
}
});
// Lambda寫法
// Future<?> taskReturn = POOL_EXECUTOR.submit(() -> task1());
System.out.println(taskReturn.get());
}
public static String task1(){
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Task1");
return "Task1";
}
}
3 執行緒池原理
3.1 ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
是原子變數,同時記錄執行緒池狀態和執行緒池執行緒個數
以32位為例,高三位表示狀態,後面表示個數
3.2 狀態
Running:接受新任務並處理阻塞佇列裡任務
shutdown:拒絕新任務但是處理佇列任務
stop:拒絕並拋棄阻塞佇列任務,同時中斷處理任務
tidying:所有任務都執行完,活動執行緒為0,將要呼叫terminated方法
terminated:終止狀態
- 狀態轉換
run->shut:顯式呼叫shutdown,隱式呼叫finalize
run/shut->stop:顯式呼叫shutdownNow
shut->tidy:執行緒池和任務佇列都為空
stop->tidy:執行緒池為空
tidy->terminated:執行terminated
3.3 執行緒池七個引數
-
corePoolSize:核心執行緒數
執行緒池維護的最小執行緒數量,核心執行緒建立後不會被回收(注意:設定allowCoreThreadTimeout=true後,空閒的核心執行緒超過存活時間也會被回收)。
大於核心執行緒數的執行緒,在空閒時間超過keepAliveTime後會被回收。
執行緒池剛建立時,裡面沒有一個執行緒,當呼叫 execute() 方法新增一個任務時,如果正在執行的執行緒數量小於corePoolSize,則馬上建立新執行緒並執行這個任務。 -
maximumPoolSize:最大執行緒數
執行緒池允許建立的最大執行緒數量。
當新增一個任務時,核心執行緒數已滿,執行緒池還沒達到最大執行緒數,並且沒有空閒執行緒,工作佇列已滿的情況下,建立一個新執行緒並執行。 -
keepAliveTime:空閒執行緒存活時間
當一個可被回收的執行緒的空閒時間大於keepAliveTime,就會被回收。
可被回收的執行緒:設定allowCoreThreadTimeout=true的核心執行緒和大於核心執行緒數的執行緒(非核心執行緒)。
unit:時間單位:keepAliveTime的時間單位:
TimeUnit.NANOSECONDS
TimeUnit.MICROSECONDS
TimeUnit.MILLISECONDS // 毫秒
TimeUnit.SECONDS
TimeUnit.MINUTES
TimeUnit.HOURS
TimeUnit.DAYS -
workQueue:工作佇列
存放待執行任務的佇列:當提交的任務數超過核心執行緒數大小後,再提交的任務就存放在工作佇列,任務排程時再從佇列中取出任務。
它僅僅用來存放被execute()方法提交的Runnable任務。工作佇列實現了BlockingQueue介面。
JDK預設的工作佇列有五種:
ArrayBlockingQueue 陣列型阻塞佇列:陣列結構,初始化時傳入大小,有界,FIFO,使用一個重入鎖,預設使用非公平鎖,入隊和出隊共用一個鎖,互斥。
LinkedBlockingQueue 連結串列型阻塞佇列:連結串列結構,預設初始化大小為Integer.MAX_VALUE,有界(近似無解),FIFO,使用兩個重入鎖分別控制元素的入隊和出隊,用Condition進行執行緒間的喚醒和等待。
SynchronousQueue 同步佇列:容量為0,新增任務必須等待取出任務,這個佇列相當於通道,不儲存元素。
PriorityBlockingQueue 優先阻塞佇列:無界,預設採用元素自然順序升序排列。
DelayQueue 延時佇列:無界,元素有過期時間,過期的元素才能被取出。 -
threadFactory:執行緒工廠
建立執行緒的工廠,可以設定執行緒名、執行緒編號等。 -
dafaultHandler:拒絕策略
當執行緒池執行緒數已滿,並且工作佇列達到限制,新提交的任務使用拒絕策略處理。可以自定義拒絕策略,拒絕策略需要實現RejectedExecutionHandler介面。
JDK預設的拒絕策略有四種:
AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。
DiscardPolicy:丟棄任務,但是不丟擲異常。可能導致無法發現系統的異常狀態。
DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新提交被拒絕的任務。
CallerRunsPolicy:由呼叫執行緒處理該任務。
3.4 mainLock成員變數
獨佔鎖,termination是