java多線程的一些筆記
多線程的目的
- 同時幹多件事;
- 充分利用CPU資源,特別是多核CPU;
- 有些場景下,n個操作需要同時執行
多線程遇到的問題/挑戰
如果n個線程之間沒有資源的共享,則多線程是沒有任何問題的,是線程安全的.
資源共享的場景:
1,讀寫相同的變量,即讀寫相同的內存區;
2,
線程的創建和銷燬會佔用資源,所以大併發情況下,一般採用線程池 參考: http://hw1287789687.iteye.com/blog/2007134
常用解決方案
- 使用對象內置鎖;
- 使用可重入鎖;
- 使用wait ,notifyAll;
- 分佈式鎖
鎖的核心作用
保證n個操作 是原子操作,即保證一個CPU時間片只能執行一個原子操作
難點
- 確認競態條件是什麼?
是變量?io?
競態條件,說得通俗一點,就是線程A 需要判斷一個變量的狀態,然後根據這個變量的狀態來執行某個操作。 在執行這個操作之前,這個變量的狀態可能會被其他線程修改。
看一個例子
import java.util.concurrent.atomic.AtomicLong;
public class LazyInitRace {
private ExpensiveObject instance=null;
public ExpensiveObject getInstance(){
if(instance==null){
instance=new ExpensiveObject();
}
return instance;
}
}
在LazyInitRace 中包含了一個競態條件,它可能會破壞這個類的正確性。假定線程A和線程B 同時執行getInstance 方法。A 看到instance 爲空,因此A創建一個新的ExpensiveObject實例。B 同樣需要判斷instance 是否爲空。此時的instance是否爲空,要取決於不可預測的時序,包括線程的調度方式,以及A 需要花多長時間來初始化ExpensiveObject並設置instance。如果當B檢查時,instance爲空,那麼在兩次調用getInstance 時可能會得到不同的對象。
線程A和B的執行時序可能是這樣的:
說明:線程B 在執行紅色部分代碼(判斷instance是否爲空)時,線程A 還沒來得執行完綠色部分的代碼。
- 什麼情況下會產生競態條件?
容易混淆的知識點
- sleep 和wait 的區別
sleep 會讓出CPU資源,但是不會釋放同步鎖;
sleep結束,繼續佔用同步鎖, 所以sleep過程中,其他線程依然阻塞;
sleep 和網絡io或文件io導致的阻塞有什麼區別呢?
wait 會讓出同步鎖(s.wait()),同時當前線程會進入 s的等待隊列,
只有調用特定的方法才能喚醒:notify
喚醒之後,並不是馬上獲取鎖,而是阻塞狀態,依然得去競爭鎖
-
yield 會降低線程的優先級嗎? 不會. yield 只是試圖讓出CPU資源,讓出之後,可能又被OS調度,又獲取到CPU資源
-
調用wait 之後,並不是阻塞狀態,而是進入了等待隊列 調用誰的wait 就會進入誰的等待隊列
-
join 本質上是調用wait wait 對應的喚醒是程序主動操作,而 join對應的喚醒 是jvm 自動操作的.
-
調用哪個方法才能創建線程? start()
-
線程可以多次啓動嗎? 只能start()一次
-
volatile 爲什麼不能解決競態條件? volatile 只能保證變量可見性; 說白了,它只能保證某一時刻的狀態是最新的, 只能保證狀態,不能保證過程,
即它保證不了操作的原子性, 可以拿數據庫的acid 進行類比理解. -
鎖有什麼作用? (1). 同一時間只能有一個線程進入鎖; (2). 保證其中的變量可見性(同volatile 作用)
加深理解
可以類比 數據庫的ACID
參考
https://my.oschina.net/huangweiindex/blog/1919895
https://mp.weixin.qq.com/s/ODJqoiHYwAhRCMnVjunsbQ