請戳GitHub原文: github.com/wangzhiwubi…java
請戳GitHub原文: github.com/wangzhiwubi…git
Java高級特性加強-多線程github
全網惟一一個從0開始幫助Java開發者轉作大數據領域的公衆號~
公衆號大數據技術與架構或者搜索import_bigdata關注,大數據學習路線最新更新,已經有不少小夥伴加入了~
本部分網絡上有大量的資源能夠參考,在這裏作了部分整理,感謝前輩的付出,每節文章末尾有引用列表,源碼推薦看JDK1.8之後的版本,注意甄別~ ####多線程 ###集合框架 ###NIO ###Java併發容器
參考文章目錄: 感謝各位大大的勞動成果~深表敬意~ blog.csdn.net/qq_34337272… blog.csdn.net/qq_34337272… www.jianshu.com/p/d53bf830f… www.jianshu.com/p/c5058b6fe…
Java併發編程這個領域中synchronized關鍵字一直都是元老級的角色,好久以前不少人都會稱它爲「重量級鎖」。可是,在JavaSE 1.6以後進行了主要包括爲了減小得到鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖以及其它各類優化以後變得在某些狀況下並非那麼重了。
「非線程安全」問題存在於「實例變量」中,若是是方法內部的私有變量,則不存在「非線程安全」問題,所得結果也就是「線程安全」的了。
若是兩個線程同時操做對象中的實例變量,則會出現「非線程安全」,解決辦法就是在方法前加上synchronized關鍵字便可。
修飾代碼塊
/**
* 同步線程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
SyncThread的調用:
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
結果以下:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
複製代碼
當兩個併發線程(thread1和thread2)訪問同一個對象(syncThread)中的synchronized代碼塊時,在同一時刻只能有一個線程獲得執行,另外一個線程受阻塞,必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。Thread1和thread2是互斥的,由於在執行synchronized代碼塊時會鎖定當前的對象,只有執行完該代碼塊才能釋放該對象鎖,下一個線程才能執行並鎖定該對象。 咱們再把SyncThread的調用稍微改一下:
Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();
複製代碼
結果以下:
SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread1:7
SyncThread1:8
SyncThread2:9
複製代碼
不是說一個線程執行synchronized代碼塊時其它的線程受阻塞嗎?爲何上面的例子中thread1和thread2同時在執行。這是由於synchronized只鎖定對象,每一個對象只有一個鎖(lock)與之相關聯,而上面的代碼等同於下面這段代碼:
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
複製代碼
這時建立了兩個SyncThread的對象syncThread1和syncThread2,線程thread1執行的是syncThread1對象中的synchronized代碼(run),而線程thread2執行的是syncThread2對象中的synchronized代碼(run);咱們知道synchronized鎖定的是對象,這時會有兩把鎖分別鎖定syncThread1對象和syncThread2對象,而這兩把鎖是互不干擾的,不造成互斥,因此兩個線程能夠同時執行。
修飾一個方法 Synchronized修飾一個方法很簡單,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修飾方法和修飾一個代碼塊相似,只是做用範圍不同,修飾代碼塊是大括號括起來的範圍,而修飾方法範圍是整個函數。
public synchronized void run() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
複製代碼
修飾一個靜態的方法 Synchronized也可修飾一個靜態方法,用法以下:
public synchronized static void method() {
// todo
}
複製代碼
咱們知道靜態方法是屬於類的而不屬於對象的。一樣的,synchronized修飾的靜態方法鎖定的是這個類的全部對象.
修飾一個類 Synchronized還可做用於一個類,用法以下:
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
複製代碼
總結:
如今咱們來看看synchronized的具體底層實現。先寫一個簡單的demo:
public class SynchronizedDemo {
public static void main(String[] args) {
synchronized (SynchronizedDemo.class) {
}
method();
}
private static void method() {
}
}
複製代碼
上面的代碼中有一個同步代碼塊,鎖住的是類對象,而且還有一個同步靜態方法,鎖住的依然是該類的類對象。編譯以後,切換到SynchronizedDemo.class的同級目錄以後,而後用javap -v SynchronizedDemo.class查看字節碼文件:
概念 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語義是一回事。
具體規則
具體規則以下:
Synchronized的happens-before規則,即監視器鎖規則:對同一個監視器的解鎖,happens-before於對該監視器的加鎖。繼續來看代碼:
public class MonitorDemo {
private int a = 0;
public synchronized void writer() { // 1
a++; // 2
} // 3
public synchronized void reader() { // 4
int i = a; // 5
} // 6
}
複製代碼
該代碼的happens-before關係如圖所示:
經過上面的討論如今咱們對Synchronized應該有所印象了,它最大的特徵就是在同一時刻只有一個線程可以得到對象的監視器(monitor),從而進入到同步代碼塊或者同步方法之中,即表現爲互斥性(排它性)。這種方式確定效率低下,每次只能經過一個線程,既然每次只能經過一個,這種形式不能改變的話,那麼咱們能不能讓每次經過的速度變快一點了。打個比方,去收銀臺付款,以前的方式是,你們都去排隊,而後去紙幣付款收銀員找零,有的時候付款的時候在包裏拿出錢包再去拿出錢,這個過程是比較耗時的,而後,支付寶解放了你們去錢包找錢的過程,如今只須要掃描下就能夠完成付款了,也省去了收銀員跟你找零的時間的了。一樣是須要排隊,但整個付款的時間大大縮短,是否是總體的效率變高速率變快了?這種優化方式一樣能夠引伸到鎖優化上,縮短獲取鎖的時間。
這裏作一個介紹,CAS爲後續鎖的章節作一個鋪墊O(∩_∩)O~
推薦文章:www.jianshu.com/p/24ffe531e… 什麼是CAS? 使用鎖時,線程獲取鎖是一種悲觀鎖策略,即假設每一次執行臨界區代碼都會產生衝突,因此當前線程獲取到鎖的時候同時也會阻塞其餘線程獲取該鎖。而CAS操做(又稱爲無鎖操做)是一種樂觀鎖策略,它假設全部線程訪問共享資源的時候不會出現衝突,既然不會出現衝突天然而然就不會阻塞其餘線程的操做。所以,線程就不會出現阻塞停頓的狀態。那麼,若是出現衝突了怎麼辦?無鎖操做是使用CAS(compare and swap)又叫作比較交換來鑑別線程是否出現衝突,出現衝突就重試當前操做直到沒有衝突爲止。
CAS的操做過程 CAS比較交換的過程能夠通俗的理解爲CAS(V,O,N),包含三個值分別爲:V 內存地址存放的實際值;O 預期的值(舊值);N 更新的新值。當V和O相同時,也就是說舊值和內存中實際的值相同代表該值沒有被其餘線程更改過,即該舊值O就是目前來講最新的值了,天然而然能夠將新值N賦值給V。反之,V和O不相同,代表該值已經被其餘線程改過了則該舊值O不是最新版本的值了,因此不能將新值N賦給V,返回V便可。當多個線程使用CAS操做一個變量是,只有一個線程會成功,併成功更新,其他會失敗。失敗的線程會從新嘗試,固然也能夠選擇掛起線程 CAS的實現須要硬件指令集的支撐,在JDK1.5後虛擬機纔可使用處理器提供的CMPXCHG指令實現。 CAS的應用場景 在J.U.C包中利用CAS實現類有不少,能夠說是支撐起整個concurrency包的實現,在Lock實現中會有CAS改變state變量,在atomic包中的實現類也幾乎都是用CAS實現,關於這些具體的實現場景在以後會詳細聊聊,如今有個印象就行了(微笑臉)。 CAS的問題
關注公衆號,內推,面試,資源下載,關注更多大數據技術~
預計更新500+篇文章,已經更新50+篇~
複製代碼