轉載請備註地址: https://blog.csdn.net/qq_3433...java
Java多線程學習(二)將分爲兩篇文章介紹synchronized同步方法另外一篇介紹synchronized同步語句塊。
系列文章傳送門:程序員
Java多線程學習(二)synchronized關鍵字(1)編程
java多線程學習(二)synchronized關鍵字(2) 安全
Java多線程學習(四)等待/通知(wait/notify)機制多線程
系列文章將被優先更新與微信公衆號<font color="red">「Java面試通關手冊」</font>,歡迎廣大Java程序員和愛好技術的人員關注。
本節思惟導圖:
思惟導圖源文件+思惟導圖軟件關注微信公衆號:<font color="red">「Java面試通關手冊」</font>回覆關鍵字:<font color="red">「Java多線程」</font>免費領取。
Java併發編程這個領域中<font color="red">synchronized關鍵字</font>一直都是元老級的角色,好久以前不少人都會稱它爲<font color="red">「重量級鎖」</font>。可是,在JavaSE 1.6以後進行了主要包括爲了<font color="red">減小得到鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖</font>以及其它各類優化以後變得在某些狀況下並非那麼重了。
這一篇文章不會介紹synchronized關鍵字的實現原理,更多的是synchronized關鍵字的使用。若是想要了解的能夠看看方騰飛的《Java併發編程的藝術》。
<font color="red">「非線程安全」</font>問題存在於<font color="red">「實例變量」</font>中,若是是<font color="red">方法內部的私有變量</font>,則不存在<font color="red">「非線程安全」</font>問題,所得結果也就是<font color="red">「線程安全」</font>的了。
若是兩個線程同時操做對象中的實例變量,則會出現<font color="red">「非線程安全」</font>,解決辦法就是在方法前加上<font color="red">synchronized關鍵字</font>便可。前面一篇文章咱們已經講過,並且貼過相應代碼,因此這裏就再也不貼代碼了。
先看例子:
<font size="2">HasSelfPrivateNum.java</font>
public class HasSelfPrivateNum { private int num = 0; synchronized public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); //若是去掉hread.sleep(2000),那麼運行結果就會顯示爲同步的效果 Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("a"); } }
<font size="2">ThreadB.java</font>
public class ThreadB extends Thread { private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("b"); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef1); athread.start(); ThreadB bthread = new ThreadB(numRef2); bthread.start(); } }
<font size="2">運行結果:</font>
a num=100停頓一會才執行
上面實例中兩個線程ThreadA和ThreadB分別訪問同一個類的不一樣實例的相同名稱的同步方法,可是效果確實異步執行。
<font color="red">爲何會這樣呢?</font>
這是由於<font color="red">synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖</font>。因此在上面的實例中,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法<font color="red">所屬對象的鎖Lock</font>,那麼其餘線程只能呈等待狀態,<font color="red">前提是多個線程訪問的是同一個對象</font>。本例中很顯然是兩個對象。
在本例中建立了兩個HasSelfPrivateNum類對象,因此就<font color="red">產生了兩個鎖</font>。當ThreadA的引用執行到addI方法中的runThread.sleep(2000)語句時,ThreadB就會<font color="red">「伺機執行」</font>。因此纔會致使執行結果如上圖所示(備註:因爲runThread.sleep(2000),「a num=100」停頓了兩秒才輸出)
經過上面咱們知道<font color="red">synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖</font>。若是多個線程訪問的是同一個對象,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法,那麼其餘線程只能呈等待狀態。若是多個線程訪問的是多個對象則不必定,由於多個對象會產生多個鎖。
<font color="red">那麼咱們思考一下當多個線程訪問的是同一個對象中的非synchronized類型方法會是什麼效果?</font>
答案是:會異步調用非synchronized類型方法,解決辦法也很簡單在非synchronized類型方法前加上synchronized關鍵字便可。
發生髒讀的狀況實在讀取實例變量時,此值已經被其餘線程更改過。
<font size="2">PublicVar.java</font>
public class PublicVar { public String username = "A"; public String password = "AA"; synchronized public void setValue(String username, String password) { try { this.username = username; Thread.sleep(5000); this.password = password; System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } catch (InterruptedException e) { e.printStackTrace(); } } //該方法前加上synchronized關鍵字就同步了 public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private PublicVar publicVar; public ThreadA(PublicVar publicVar) { super(); this.publicVar = publicVar; } @Override public void run() { super.run(); publicVar.setValue("B", "BB"); } }
<font size="2">Test.java</font>
public class Test { public static void main(String[] args) { try { PublicVar publicVarRef = new PublicVar(); ThreadA thread = new ThreadA(publicVarRef); thread.start(); Thread.sleep(200);//打印結果受此值大小影響 publicVarRef.getValue(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">運行結果:</font>
解決辦法:getValue()方法前加上synchronized關鍵字便可。
<font size="2">加上synchronized關鍵字後的運行結果:</font>
<font color="red">「可重入鎖」</font>概念是:<font color="red">本身能夠再次獲取本身的內部鎖</font>。好比一個線程得到了某個對象的鎖,此時這個對象鎖尚未釋放,當其再次想要獲取這個對象的鎖的時候仍是能夠獲取的,若是不可鎖重入的話,就會形成死鎖。
<font size="2">Service.java</font>
public class Service { synchronized public void service1() { System.out.println("service1"); service2(); } synchronized public void service2() { System.out.println("service2"); service3(); } synchronized public void service3() { System.out.println("service3"); } }
<font size="2">MyThread.java</font>
public class MyThread extends Thread { @Override public void run() { Service service = new Service(); service.service1(); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }
<font size="2">運行結果:</font>
另外<font color="red">可重入鎖也支持在父子類繼承的環境中</font>
<font size="2">Main.java:</font>
public class Main { public int i = 10; synchronized public void operateIMainMethod() { try { i--; System.out.println("main print i=" + i); Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">Sub.java:</font>
public class Sub extends Main { synchronized public void operateISubMethod() { try { while (i > 0) { i--; System.out.println("sub print i=" + i); Thread.sleep(100); this.operateIMainMethod(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
<font size="2">MyThread.java:</font>
public class MyThread extends Thread { @Override public void run() { Sub sub = new Sub(); sub.operateISubMethod(); } }
<font size="2">Run.java:</font>
public class Run { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }
<font size="2">運行結果:</font>
說明當存在父子類繼承關係時,子類是徹底能夠經過「可重入鎖」調用父類的同步方法。
另外出現異常時,其鎖持有的鎖會自動釋放。
若是父類有一個帶synchronized關鍵字的方法,子類繼承並重寫了這個方法。
可是同步不能繼承,因此仍是須要在子類方法中添加synchronized關鍵字。
參考:
《Java多線程編程核心技術》
歡迎關注個人微信公衆號:"Java面試通關手冊"(分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取):