當多個線程涉及到共享數據的時候,就會設計到線程安全的問題。非線程安全其實會在多個線程對同一個對象中的實例變量進行併發訪問時發生,產生的後果就是「髒讀」。發生髒讀,就是取到的數據已經被其餘的線程改過了。什麼是線程安全呢?用併發編程實戰裏面的一段話解釋說:html
當多個線程訪問某個類時,無論運行時環境採用何種調度方式或者這些線程將如何交替執行,而且在主調代碼中不須要額
外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的
這裏須要注意的是多個線程,若是一個線程確定是線程安全的,並且這裏的共享數據是指成員變量,不是局部變量,局部變量是java
方法私有的,而方法運行時,對應的虛擬機方法棧是線程私有的,因此局部變量必定是方法安全的。編程
爲了保證線程的安全,就要用到同步了。同步能夠這麼理解,只有等一個線程執行完這麼一段須要同步的代碼,其餘的線程才能執行。而異步就是這段代碼代碼能夠交替執行。安全
synchronized同步方法的用法是:多線程
synchronized 修飾符 返回值 方法名(){ }
1.synchronized取得的鎖都是對象鎖,而不是把一段代碼或函數當作鎖併發
synchronized方法至關於給這個方法上了一把鎖,鎖就是擁有這個方法的實例對象,當多個線程訪問一個類的同一個實例對象時,這個鎖也就是這個實例對象,先得到這把鎖的線程就能夠執行同步方法裏面的內容,其餘線程只有等第一個線程執行結束自動釋放鎖或者程序拋出異常或者使用wait()等方法釋放鎖的狀況下才能得到鎖。app
當多個線程訪問一個類的多個實例對象時,jvm就建立了多把鎖,多個線程獲取到的鎖不同。這時候同步方法仍是異步執行的。異步
2.synchronized方法鎖重入jvm
鎖重入的意思是,一個線程已經擁有了這個對象的鎖,再次請求該對象鎖時,仍是會保證成功,也就是說,在synchronized方法裏面,再調用本類中的其餘的synchronized方法,是永遠能夠獲得鎖的。不然,會形成死鎖。函數
3.出現異常鎖會自動釋放
4.同步不具備繼承性
也就是說父類中方法是同步的,子類繼承父類的方法,這個方法就不是同步的了,須要再加上synchroized變成同步方法
5.若是多個線程持有一把鎖,也就是隻有一個實例對象,那麼該對象裏面的全部synchroized方法都具備同步性,也就是,當一個線程調用其中一個sycnhroized方法時,其餘線程調用這個對象裏面的其餘synchroized也會處於阻塞狀態。
使用synchroized方法有什麼弊端呢?從運行時間來看,當一個線程取得鎖之後,其餘線程只有等待它釋放鎖之後才能執行方法裏面的代碼,從運行時間來看,這樣會浪費很長的時間,怎麼改變呢?就要用到同步語句塊。
同步代碼塊如何解決上面問題呢?那就是隻將須要同步的方法用
synchroized(this|任意對象|class){ }
括起來。括號裏面的內容是一個監視器
只有代碼塊裏面的代碼是同步的,其他的代碼仍是異步的。
1、
1.當括號裏面用this時,鎖定的也是當前對象。
這時候其實和使用synchroized方法同樣。
2.當括號裏面是任意對象時。
當多個線程持有的對象監視器爲同一個的前提下,如上。
可是當多個線程持有對象監視器爲多個時,因爲對象監視器不一樣,因此運行結果就是異步的。同步代碼塊放在非同步synchronized方法中進行生命,並不能保證調用方法的線程的執行同步/順序性,也就是線程調用方法的順序是無需的,雖然在同步塊中執行的順序是同步的,這樣極其容易出現髒讀。
因此最好保證對象監視器是同一個對象。若是使用同步代碼塊鎖非this對象,則同步代碼塊中的程序與同步方法時異步的,不予其餘鎖this同步方法爭搶this鎖,大大提升運行效率。
2、synchroized(任意對象)的三個結論
1.多個線程同時執行時呈同步效果
2.當其餘線程執行任意對象中synchronized同步方法時呈同步效果
3.當其餘線程執行任意對象裏面的synchronized(this)代碼塊時呈同步效果
同步方法比同步代碼塊更高效,可是它們的功能是同樣的(有人研究,從虛擬機測試)。
synchronized用於解決同步問題,當有多條線程同時訪問共享數據時,若是不進行同步,就會發生錯誤,java提供的解決方案是:只要將操做共享數據的語句在某一時段讓一個線程執行完,在執行過程當中,其餘線程不能進來執行能夠。解決這個問題。這裏在用synchronized時會有兩種方式,一種是上面的同步方法,即用synchronized來修飾方法,另外一種是提供的同步代碼塊。
import javax.print.DocFlavor; public class SynchronsizeObj{ StringBuilder stringBuilder = new StringBuilder(); public synchronized void synMethod () { try { Thread.sleep(100); for(int i = 0;i < 10; i++) { stringBuilder.append("synmethod:" + String.valueOf(i)); } stringBuilder.append("\n"); System.out.println(stringBuilder.toString()); } catch (InterruptedException ex) { ex.printStackTrace(); } } public void synThis() { synchronized (this) { for(int i = 0;i < 10; i++) { stringBuilder.append("synthis:" + String.valueOf(i)); } stringBuilder.append("\n"); System.out.println(stringBuilder.toString()); } } public void synParam() { synchronized (stringBuilder) { try { Thread.sleep(0); for(int i = 0;i < 10; i++) { stringBuilder.append("synparam:" + String.valueOf(i)); } stringBuilder.append("\n"); System.out.println(stringBuilder.toString()); } catch (InterruptedException ex) { ex.printStackTrace(); } } } }
上面的代碼所有是加了同步鎖,則此時輸出結果爲:
上面是同步鎖類
測試代碼爲:
/** * @author :dongbl * @version : * @Description: * @date :19:48 2017/11/14 */ public class TestSyn { public static void main(String [] args){ final SynchronsizeObj obj = new SynchronsizeObj(); new Thread(new Runnable() { public void run() { obj.synMethod(); } }).start(); new Thread(new Runnable() { public void run() { obj.synThis(); } }).start(); new Thread(new Runnable() { public void run() { obj.synParam(); } }).start(); } }
運行結果爲:
synparam:0synparam:1synparam:2synparam:3synparam:4synparam:5synparam:6synparam:7synparam:8synparam:9 synmethod:0synmethod:1synmethod:2synmethod:3synmethod:4synmethod:5synmethod:6synmethod:7synmethod:8synmethod:9 synthis:0synthis:1synthis:2synthis:3synthis:4synthis:5synthis:6synthis:7synthis:8synthis:9
在啓動線程1調用方法synMethod後,接着會讓線程1休眠100豪秒鐘,這時會調用方法synparam,注意到方法C這裏用synchronized進行加鎖,這裏鎖的對象是str這個字符串對象。可是方法B則不一樣,是用當前對象this進行加鎖。顯然,這兩個方法用的是一把鎖
這就是同步對象和同步變量的區別。
同步方法直接在方法上加synchronized實現加鎖,同步代碼塊則在方法內部加鎖,很明顯,同步方法鎖的範圍比較大,而同步代碼塊範圍要小點,通常同步的範圍越大,性能就越差,通常須要加鎖進行同步的時候,確定是範圍越小越好,這樣性能更好*。
synchronized 方法控制對類成員變量的訪問:每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放。
此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(由於至多隻有一個可以得到該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要全部可能訪問類成員變量的方法均被聲明爲 synchronized)
synchronized 方法的缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明爲synchronized ,因爲在線程的整個生命期內它一直在運行,所以將致使它對本類任何 synchronized 方法的調用都永遠不會成功。固然咱們能夠經過將訪問類成員變量的代碼放到專門的方法中,將其聲明爲 synchronized ,並在主方法中調用來解決這一問題,可是 Java 爲咱們提供了更好的解決辦法,那就是 synchronized 塊。
synchronized 方法的缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明爲synchronized ,因爲在線程的整個生命期內它一直在運行,所以將致使它對本類任何 synchronized 方法的調用都永遠不會成功。固然咱們能夠經過將訪問類成員變量的代碼放到專門的方法中,將其聲明爲 synchronized ,並在主方法中調用來解決這一問題,可是 Java 爲咱們提供了更好的解決辦法,那就是 synchronized 塊。
對synchronized(this)的一些理解
1、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。
2、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該object中的非synchronized(this)同步代碼塊。
3、尤爲關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其它synchronized(this)同步代碼塊的訪問將被阻塞。
4、第三個例子一樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就得到了這個object的對象鎖。結果,其它線程對該object對象全部同步代碼部分的訪問都被暫時阻塞。
一、java中的synchronized(同步代碼塊和同步方法的區別)
文中對生產者和消費者模式進行詳細的介紹,關鍵是對於wait()、notify()、notifyALL(),進行詳細的介紹。