1.爲何會用到併發java
充分利用多核CPU的計算能力程序員
方便進行業務拆分,提高應用性能面試
面對複雜業務模型,並行程序會比串行程序更適應業務需求,而併發編程更能吻合這種業務拆分算法
2.併發編程缺點數據庫
頻繁上下文切換編程
無鎖併發編程:能夠參照concurrentHashMap鎖分段的思想,不一樣的線程處理不一樣段的數據,這樣在多線程競爭的條件下,能夠減小上下文切換的時間。數組
CAS算法,利用Atomic下使用CAS算法來更新數據,使用了樂觀鎖,能夠有效的減小一部分沒必要要的鎖競爭帶來的上下文切換緩存
使用最少線程:避免建立不須要的線程,好比任務不多,可是建立了不少的線程,這樣會形成大量的線程都處於等待狀態安全
協程:在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換性能優化
那麼,一般能夠用以下方式避免死鎖的狀況:
3.須要瞭解的概念
3.1 同步與異步
同步和異步一般用來形容一次方法調用。同步方法調用一開始,調用者必須等待被調用的方法結束後,調用者後面的代碼才能執行。而異步調用,指的是,調用者不用管被調用方法是否完成,都會繼續執行後面的代碼,當被調用的方法完成後會通知調用者。
3.2 併發與並行
併發和並行是十分容易混淆的概念。併發指的是多個任務交替進行,而並行則是指真正意義上的「同時進行」。實際上,若是系統內只有一個CPU,而使用多線程時,那麼真實系統環境下不能並行,只能經過切換時間片的方式交替進行,而成爲併發執行任務。真正的並行也只能出如今擁有多個CPU的系統中。
3.3 阻塞和非阻塞
阻塞和非阻塞一般用來形容多線程間的相互影響,好比一個線程佔有了臨界區資源,那麼其餘線程須要這個資源就必須進行等待該資源的釋放,會致使等待的線程掛起,這種狀況就是阻塞,而非阻塞就剛好相反,它強調沒有一個線程能夠阻塞其餘線程,全部的線程都會嘗試地往前運行。
3.4 臨界區
臨界區用來表示一種公共資源或者說是共享數據,能夠被多個線程使用。可是每一個線程使用時,一旦臨界區資源被一個線程佔有,那麼其餘線程必須等待。
4.新建線程
一個java程序從main()方法開始執行,而後按照既定的代碼邏輯執行,看似沒有其餘線程參與,但實際上java程序天生就是一個多線程程序,包含了:(1)分發處理髮送給給JVM信號的線程;(2)調用對象的finalize方法的線程;(3)清除Reference的線程;(4)main線程,用戶程序的入口。那麼,如何在用戶程序中新建一個線程了,只要有三種方式:
經過繼承Thread類,重寫run方法;
經過實現runable接口;
經過實現callable接口這三種方式,下面看具體demo。
public class CreateThread { public static void main(String[] args) { //繼承Thread Thread thread = new Thread(){ @Override public void run() { System.out.println("繼承Thread"); super.run(); } }; thread.start(); //實現Runnable接口 Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("實現Runnable接口"); } }); thread1.start(); //經過callable接口實現 ExecutorService service = Executors.newSingleThreadExecutor(); Future<String> future = service.submit(new Callable<String>() { @Override public String call() throws Exception { return "經過callable接口實現"; } }); try { String result = future.get(); System.out.println(result); }catch (InterruptedException e){ e.printStackTrace(); }catch (ExecutionException e){ e.printStackTrace(); } } }
5. 線程的狀態
此圖來源於《JAVA併發編程的藝術》一書中,線程是會在不一樣的狀態間進行轉換的,java線程線程轉換圖如上圖所示。線程建立以後調用start()方法開始運行,當調用wait(),join(),LockSupport.lock()方法線程會進入到WAITING狀態,而一樣的wait(long timeout),sleep(long),join(long),LockSupport.parkNanos(),LockSupport.parkUtil()增長了超時等待的功能,也就是調用這些方法後線程會進入TIMED_WAITING狀態,當超時等待時間到達後,線程會切換到Runable的狀態,另外當WAITING和TIMED _WAITING狀態時能夠經過Object.notify(),Object.notifyAll()方法使線程轉換到Runable狀態。當線程出現資源競爭時,即等待獲取鎖的時候,線程會進入到BLOCKED阻塞狀態,當線程獲取鎖時,線程進入到Runable狀態。線程運行結束後,線程進入到TERMINATED狀態,狀態轉換能夠說是線程的生命週期。另外須要注意的是:
用一個表格將上面六種狀態進行一個總結概括。
6.線程狀態的基本操做
6.1 interrupted
6.2 join
join方法能夠看作是線程間協做的一種方式,不少時候,一個線程的輸入可能很是依賴於另外一個線程的輸出.若是一個線程實例A執行了threadB.join(),其含義是:當前線程A會等待threadB線程終止後threadA纔會繼續執行。關於join方法一共提供以下這些方法:
public final synchronized void join(long millis) public final synchronized void join(long millis, int nanos) public final void join() throws InterruptedException
public class TestJoin { public static void main(String[] args) { Thread previousThread = Thread.currentThread(); for (int i = 0; i <= 10; i++) { Thread curThread = new JoinThread(previousThread, i); curThread.start(); previousThread = curThread; } } static class JoinThread extends Thread { private Thread thread; private int i; public JoinThread(Thread thread, int i) { this.thread = thread; this.i = i; } @Override public void run() { try { thread.join(); System.out.println(thread.getName() + " terminated" + ">>>" + "i=" + i); } catch (InterruptedException e) { e.printStackTrace(); } } } } 輸出結果: main terminated>>>i=0 Thread-0 terminated>>>i=1 Thread-1 terminated>>>i=2 Thread-2 terminated>>>i=3 Thread-3 terminated>>>i=4 Thread-4 terminated>>>i=5 Thread-5 terminated>>>i=6 Thread-6 terminated>>>i=7 Thread-7 terminated>>>i=8 Thread-8 terminated>>>i=9 Thread-9 terminated>>>i=10
在上面的例子中一個建立了10個線程,每一個線程都會等待前一個線程結束纔會繼續運行。能夠通俗的理解成接力,前一個線程將接力棒傳給下一個線程,而後又傳給下一個線程......
6.3 sleep
二者主要的區別:
6.5 守護線程Daemon
如圖爲JMM抽象示意圖,線程A和線程B之間要完成通訊的話,要經歷以下兩步:
從橫向去看看,線程A和線程B就好像經過共享變量在進行隱式通訊。這其中有頗有意思的問題,若是線程A更新後數據並無及時寫回到主存,而此時線程B讀到的是過時的數據,這就出現了「髒讀」現象。能夠經過同步機制(控制不一樣線程間操做發生的相對順序)來解決或者經過volatile關鍵字使得每次volatile變量都可以強制刷新到主存,從而對每一個線程都是可見的。
10.1 .happens-before定義
happens-before的概念最初由Leslie Lamport在其一篇影響深遠的論文(《Time,Clocks and the Ordering of Events in a Distributed System》)中提出,有興趣的能夠google一下。JSR-133使用happens-before的概念來指定兩個操做之間的執行順序。因爲這兩個操做能夠在一個線程以內,也能夠是在不一樣線程之間。所以,JMM能夠經過happens-before關係向程序員提供跨線程的內存可見性保證(若是A線程的寫操做a與B線程的讀操做b之間存在happens-before關係,儘管a操做和b操做在不一樣的線程中執行,但JMM向程序員保證a操做將對b操做可見)。具體的定義爲:
1)若是一個操做happens-before另外一個操做,那麼第一個操做的執行結果將對第二個操做可見,並且第一個操做的執行順序排在第二個操做以前。
2)兩個操做之間存在happens-before關係,並不意味着Java平臺的具體實現必需要按照happens-before關係指定的順序來執行。若是重排序以後的執行結果,與按happens-before關係來執行的結果一致,那麼這種重排序並不非法(也就是說,JMM容許這種重排序)。
上面的1)是JMM對程序員的承諾。從程序員的角度來講,能夠這樣理解happens-before關係:若是A happens-before B,那麼Java內存模型將向程序員保證——A操做的結果將對B可見,且A的執行順序排在B以前。注意,這只是Java內存模型向程序員作出的保證!
上面的2)是JMM對編譯器和處理器重排序的約束原則。正如前面所言,JMM實際上是在遵循一個基本原則:只要不改變程序的執行結果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎麼優化都行。JMM這麼作的緣由是:程序員對於這兩個操做是否真的被重排序並不關心,程序員關心的是程序執行時的語義不能被改變(即執行結果不能被改變)。所以,happens-before關係本質上和as-if-serial語義是一回事。as-if-serial語義的意思是:無論怎麼重排序(編譯器和處理器爲了提供並行度),(單線程)程序的執行結果不能被改變。編譯器,runtime和處理器都必須遵照as-if-serial語義。as-if-serial語義把單線程程序保護了起來,遵照as-if-serial語義的編譯器,runtime和處理器共同爲編寫單線程程序的程序員建立了一個幻覺:單線程程序是按程序的順序來執行的。
as-if-serial VS happens-before
10.2 具體規則
具體的一共有六項規則:
11. 總結
JMM是語言級的內存模型,在個人理解中JMM處於中間層,包含了兩個方面:(1)內存模型;(2)重排序以及happens-before規則。同時,爲了禁止特定類型的重排序會對編譯器和處理器指令序列加以控制。而上層會有基於JMM的關鍵字和J.U.C包下的一些具體類用來方便程序員可以迅速高效率的進行併發編程。站在JMM設計者的角度,在設計JMM時須要考慮兩個關鍵因素:
另外還要一個特別有意思的事情就是關於重排序問題,更簡單的說,重排序能夠分爲兩類:
JMM對這兩種不一樣性質的重排序,採起了不一樣的策略,以下。
JMM的設計圖爲:
從圖能夠看出:
一個happens-before規則對應於一個或多個編譯器和處理器重排序規則。對於Java程序員來講,happens-before規則簡單易懂,它避免Java程序員爲了理解JMM提供的內存可見性保證而去學習複雜的重排序規則以及這些規則的具體實現方法
原文地址
https://www.jianshu.com/p/959cf355b574https://www.jianshu.com/p/f65ea68a4a7fhttps://www.jianshu.com/p/d52fea0d6ba5