本篇文章做爲Java併發系列的第一篇,並不會介紹相應的api,只是簡單的提到多線程關鍵線程的通訊和同步,後續文章會詳細介紹其中的原理編程
由於Java線程採用這種一對一映射內核線程方式實現,因此Java線程調度沒法經過設置優先級控制(線程調度採用搶佔式)api
狀態 | 說明 |
---|---|
NEW | 初始狀態,線程被構建尚未調用start()方法 |
RUNNABLE | 運行狀態,Java中運行和就緒統稱運行中,可能正在執行,也可能在等待CPU時間片 |
WAITING | 等待狀態,這種狀態的線程不會被分配CPU時間片,須要等待其餘線程顯式喚醒 |
TIMED_WAITING | 超時等待狀態,不一樣於等待狀態的是在必定時間以後它們會由系統自動喚醒 |
BLOCKED | 阻塞狀態,表示線程在等待對象的monitor鎖,試圖經過synchronized去獲取某個鎖 |
TERMINATED | 終止狀態,表示當前線程已執行完畢 |
💡小提示: 操做系統中的運行和就緒兩個狀態在Java中合併成運行狀態,LockSupport類的park()方法會使當前線程進入等待狀態,因爲併發包中的ReentrantLock和condition等併發工具使用的是LockSupport的park(),因此阻塞於它們的線程不一樣於阻塞於synchronized的線程是處於阻塞狀態,而是等待狀態;關於這些狀態的轉換你們能夠寫個小demo試一試,利用jstack查看線程狀態。 緩存
推薦使用線程池管理線程,線程池有如下好處bash
💡小提示:儘可能不要使用Executors來建立線程池,由於使用的無界隊列會發生內存溢出,具體緣由後續章節會介紹多線程
💡小提示:線程私有的本地內存是一個緩存、寫緩衝等的抽象概念併發
Java中的線程通訊方式有如下幾種:工具
線程間的通訊方式:共享內存、消息隊列;學習
//等待通知模式的經典範式:
synchronized (object) {
while (boolean) {
object.wait();
}
doSomeThing();
}
synchronized (object) {
change boolean;
object.notify();
}
複製代碼
💡小提示:等待處使用while而不是if是爲了防止錯誤或者提早的通知 ui
Java中實現線程同步的方式:spa
除了synchronized其它幾種同步方式的實現都與AQS有關,後續文章中會詳細介紹,下面給你們來個demo感覺下本篇文章的內容。
/**
* @author XiaMu
*/
public class FutureTaskDemo {
private static volatile Integer count = 0;
private static CountDownLatch countDownLatch = new CountDownLatch(500);
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 不推薦,爲了方便使用了
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<FutureTask<Integer>> resultList = new ArrayList<>(500);
for (int i = 0; i < 500; i++) {
FutureTask<Integer> task = new FutureTask<>(() -> {
// lock.lock();(1)
int result = count++;
// lock.unlock();(1)
// countDownLatch.countDown();(2)
return result;
});
executorService.submit(task);
resultList.add(task);
}
// countDownLatch.await();(2)
System.out.println("第一處計算結果:" + count);
// 爲了查看每一個任務的執行結果
Map<Integer, Integer> resultMap = new HashMap<>(500);
for (FutureTask<Integer> result : resultList) { // (3)
Integer sum = result.get();
if (resultMap.containsKey(sum)) {
System.out.println(sum + "計算過了");
} else {
resultMap.put(sum, 1);
}
}
System.out.println("第二處計算結果:" + count);
executorService.shutdown();
}
}
複製代碼
取兩次執行結果:
第一處計算結果:0 第一處計算結果:270
135計算過了 492計算過了
163計算過了 16計算過了
454計算過了 437計算過了
458計算過了 445計算過了
463計算過了 第二處計算結果:496
470計算過了
317計算過了
319計算過了
第二處計算結果:492
複製代碼
💡:毫無疑問,計算結果出現了錯誤,能夠將(1)註釋打開加鎖保證計算正確。第一處計算結果不等於第二處是由於主線程執行到第一處時,子任務並無執行完。若是沒有(3)的for循環中獲取結果的Future.get()阻塞主線程,那麼第二處計算結果打印時子任務可能還沒執行完。註釋(2)打開能夠解決兩次打印不一致問題,由於CountDownLatch會將主線程阻塞直到第500個子任務執行完畢。
💡:使用集合時最好根據狀況指定初始容量,不然在後續的操做中會發生頻繁擴容影響效率
【本篇文章只是講了個大概,若是小夥伴們發現有問題或者疑惑的地方歡迎指出,一塊兒學習進步!】
參考:《Java併發編程的藝術》《碼出高效》