做者:享學課堂James老師java
轉載請聲明出處!程序員
Java程序員,你必須得知道併發編程概念面試
你們好,我是享學課堂風騷走位的James, 併發編程做爲Java編程的核心靈魂, 無論在面試仍是在工做中,都是很是重要的, 花了很多時間我整理出了併發編程的一個核心知識, 但願可以幫助更多的Java開發人員,在工做中合理使用多線程, 讓你的代碼更高效, 更直觀。大概分爲如下板塊。編程
目錄數組
► 簡介安全
► 概念bash
►Java內存模型:Happens-before 關係數據結構
► 標準同步功能多線程
► 安全發佈併發
► 對象不可變
► 線程Thread類
► 線程的活躍度:死鎖與活鎖
► java.util.concurrent包
所謂併發編程是指在一臺處理器上「同時」處理多個任務。併發是在同一實體上的多個事件。這段是從百度百科找到的解釋, 而個人解釋, 你所寫的任何一行代碼, 它的執行都是用線程來運行的, 也就是說併發編程直接決定了你係統性能運行狀況, 能讓你的系統起飛, 也能讓你的系統性能變成蝸牛。
從JDK的最低版本開始,Java就支持了線程和鎖等關鍵併發概念。那麼這些概念務必記到你腦海深處,哈哈.
當多個線程對共享資源執行一系列操做時, 它們會對競爭共享資源, 每一個線程的操做順序不同, 會致使多個不可預期的操做結果。好比如下的代碼就爲非線程安全的,其中 value
能夠屢次初始化,由於 if(value==null)
作了null 判斷, 而後初始化的, 延遲初始化的字段不是原子的:
class JamesLazy <T> {
private volatile T value;
T get() {
if (value == null)//這裏作了null判斷, 延遲初始化的字段它不是原子的
value = initialize();
return value;
}
}
複製代碼
當兩個或多個線程在沒有同步的狀況下嘗試訪問相同的非final變量時,就會發生數據競爭。不使用同步可能致使你的全部操做對其它線程是不可見的,所以能夠讀取過期數據,但若反過來可能會產生無限循環,損壞的數據結構或不許確的計算等後果。此代碼可能會致使無限循環,由於讀者線程可能永遠不會觀察到寫入器線程所作的更改:
class JamesWaiter implements Runnable {
private Boolean shouldFinish;
void finish() {
shouldFinish = true;
}
public void run() {
long iteration = 0;
while (!shouldFinish) {
iteration++;
}
System.out.println("完成後的結果: " + iteration);
}
}
class JamesDataRace {
public static void main(String[] args) throws InterruptedException {
JamesWaiter waiter = new JamesWaiter();
Thread waiterThread = new Thread(waiter);
waiterThread.start();
waiter.finish();
waiterThread.join();
}
}
複製代碼
運行結果爲:
Java內存模型是根據讀取和寫入字段以及在監聽器上同步等操做定義的。能夠經過Happens-before關係對操做進行排序,通常用於推斷線程什麼時候看到另外一個線程的操做結果,以及用來分析同步的程序構成情況。
在下圖中,Thread 1的 ActionX
操做在 ActionY
操做以前就調用了,所以在 Thread2
中全部操做 ActionY
的右側業務操做時, 將會看到Thread 1中Action X前的全部操做。
synchronized
關鍵字用於防止不一樣的線程同時執行相同的代碼塊, 其實就是指這個代碼塊只被一個線程執行。當線程A得到synchronized鎖後,只有線程A訪問synchronized代碼塊,是一種獨佔式模式操做, 例如: 13號技師被王根基同窗帶進屋後,王根基同窗在門上掛了把鎖, 其它線程得等待, 保證了13號技師只能與王根基(線程)進行業務操做 ,不難看出這個操做原子操做(只有王根基線程操做13號技師)。此外,它保證其它線程在獲取相同的鎖以後將觀察正在操做線程的結果,什麼時候釋放鎖。
class JamesAtomicOperation {
private int counter0 ;
private int counter1 ;
void increment(){
synchronized(this){
counter0 ++ ;
counter1 ++ ;
}
}
}
複製代碼
synchronized
關鍵字能夠在方法級來指定。
鎖是可重入的,所以若是線程已經擁有鎖,能夠再次成功獲取它。
class JamesReentrantcy {
synchronized void doAll(){
doFirst();
doSecond();
}
synchronized void doFirst(){
System.out.println(「第一次操做成功。」);
}
synchronized void doSecond(){
System.out.println(「第二次操做成功。」);
}
}
複製代碼
wait/notify/notifyAll
方法在 Object
類中聲明。wait
的做用可使線程狀態變成 WAITING
或 TIMED_WAITING
(若是已等待超時)狀態。爲了喚醒一個線程,能夠執行如下任何操做:
另外一個線程調用 notify
,喚醒在監視器上等待的任意線程。
另外一個線程調用 notifyAll
,喚醒監視器上等待的全部線程。
若是調用 Thread#interrupt
。在這種狀況下,會拋出 InterruptedException
。
最多見的模式是條件循環:
class JamesConditionLoop {
private Boolean condition;
synchronized void waitForCondition()throws InterruptedException {
while(!condition){
wait();
}
}
synchronized void satisfCondition(){
condition = true ;
notifyAll();
}
}
複製代碼
同志們請記住,若是 wait/notify/notifyAll
要在你的對象上使用,須要首先獲取此對象鎖。
老是在一個循環中等待檢查正在等待的條件 - 若是另外一個線程在等待開始以前知足條件,這就解決了時間問題。此外,它還能夠保護您的代碼免受可能(而且確實發生)的虛假喚醒。
在打電話以前,請務必確保您知足等待條件 notify/notifyAll
。若是不這樣作將致使通知,但沒有線程可以逃脫其等待循環。
volatile
解決了多線程之間的資源可見性問題,有這麼一層關係你們須要知道:對某個volatile字段的寫操做happens-before後續對同一個volatile字段的讀操做,好比線程1寫入了volatile變量v(這裏和後續的「變量」都指的是對象的字段、類字段和數組元素),接着線程2讀取了v,那麼,線程1寫入v及以前的寫操做都對線程2可見(線程1和線程2能夠是同一個線程)。所以,它保證字段的任何後續讀取都將看到由最近寫入設置的值。
class JamesVolatileFlag implements Runnable {
private volatile Boolean shouldStop;
public void run() {
while (!shouldStop) {
// TODO業務代碼
}
System.out.println("中止.");
}
void stop() {
shouldStop = true;
}
public static void main(String[] args) throws InterruptedException {
JamesVolatileFlag flag = new JamesVolatileFlag();
Thread thread = new Thread(flag);
thread.start();
flag.stop();
thread.join();
}
}
複製代碼
在 java.util.concurrent.atomic
包路徑下的一些類以無鎖方式支持單個值上的原子複合操做,相似於volatile。
使用AtomicXXX類,能夠實現原子 check-then-act
操做:
class JamesCheckThenAct {
private final AtomicReference<String> value = new AtomicReference<>();
void initialize() {
if (value.compareAndSet(null, "value")) {
System.out.println("僅初始化一次.");
}
}
}
複製代碼
jdk8中新增的 AtomicInteger
和 AtomicLong
具備 increment/decrement
(自增自減)的原子操做:
class JamesIncrement {
private final AtomicInteger state = new AtomicInteger();
void advance() {
int oldState = state.getAndIncrement();
System.out.println("Advanced: '" + oldState + "' -> '" + (oldState + 1) + "'.");
}
}
複製代碼
若計數器不須要原子操做來獲取其值,請考慮使用 LongAdder
而不是 AtomicLong
/ AtomicInteger
類。LongAdder
其實就是把一個變量分解爲多個變量,讓一樣多的線程去競爭多個資源,性能問題就能夠解決了,所以它在高爭用下表現更好。
從概念上講,ThreadLocal就好像在每一個Thread中都有一個具備本身版本的變量。ThreadLocal一般用於存儲每一個線程的值,如「當前事務」或其餘資源。此外,它們還用於維護每線程計數器,統計信息或ID生成器。
class JamesTransactionManager {
private final ThreadLocal<Transaction> currentTransaction = ThreadLocal.withInitial(NullTransaction::new);
Transaction currentTransaction() {
Transaction current = currentTransaction.get();
if (current.isNull()) {
current = new TransactionImpl();
currentTransaction.set(current);
}
return current;
}
}
複製代碼
好了, 第1篇就到這裏,但願你們持續關注更新…………