快速鳥瞰併發編程, 嘔心瀝血整理的架構技術【1】

做者:享學課堂James老師java

轉載請聲明出處!程序員

Java程序員,你必須得知道併發編程概念面試

你們好,我是享學課堂風騷走位的James, 併發編程做爲Java編程的核心靈魂, 無論在面試仍是在工做中,都是很是重要的, 花了很多時間我整理出了併發編程的一個核心知識, 但願可以幫助更多的Java開發人員,在工做中合理使用多線程, 讓你的代碼更高效, 更直觀。大概分爲如下板塊。編程

目錄數組

► 簡介安全

► 概念bash

►Java內存模型:Happens-before 關係數據結構

► 標準同步功能多線程

► 安全發佈併發

► 對象不可變

► 線程Thread類

► 線程的活躍度:死鎖與活鎖

► java.util.concurrent包

第1節 簡介

所謂併發編程是指在一臺處理器上「同時」處理多個任務。併發是在同一實體上的多個事件。這段是從百度百科找到的解釋, 而個人解釋, 你所寫的任何一行代碼, 它的執行都是用線程來運行的, 也就是說併發編程直接決定了你係統性能運行狀況, 能讓你的系統起飛, 也能讓你的系統性能變成蝸牛。

第2節 概念

從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();
	}
}
複製代碼

運行結果爲:

第3節 Java內存模型:Happens-before 關係

Java內存模型是根據讀取和寫入字段以及在監聽器上同步等操做定義的。能夠經過Happens-before關係對操做進行排序,通常用於推斷線程什麼時候看到另外一個線程的操做結果,以及用來分析同步的程序構成情況。

在下圖中,Thread 1的 ActionX操做在 ActionY操做以前就調用了,所以在 Thread2中全部操做 ActionY的右側業務操做時, 將會看到Thread 1中Action X前的全部操做。

第4節 標準同步功能

synchronized關鍵字

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的做用可使線程狀態變成 WAITINGTIMED_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解決了多線程之間的資源可見性問題,有這麼一層關係你們須要知道:對某個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中新增的 AtomicIntegerAtomicLong具備 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

從概念上講,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篇就到這裏,但願你們持續關注更新…………

相關文章
相關標籤/搜索