在多線程中,同步與死鎖概念很重要,在本章中必須了解以下幾點:
1)哪里需要同步。
2)如何實現同步,了解代碼即可。
3)及實現同步后有哪些副作用。
代碼並不要求可以完整編寫,但是概念必須清楚。
具體內容
1.1問題引出
以買火車票為例,不管多少地方可以買火車票,最終一趟列車的車票數量是固定的,如果把各個售票點理解為線程的話,則所有線程應該共同擁有同一份票數。
package Thread1; class MyThread implements Runnable{ private int ticket = 5 ; // 假設一共有5張票 public void run(){ for(int i=0;i<100;i++){ if(ticket>0){ // 還有票 System.out.println("賣票:ticket = " + ticket-- ); } } } }; public class demo1{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義線程對象 Thread t1 = new Thread(mt) ; // 定義Thread對象 Thread t2 = new Thread(mt) ; // 定義Thread對象 Thread t3 = new Thread(mt) ; // 定義Thread對象 t1.start() ; t2.start() ; t3.start() ; } };
運行結果:
賣票:ticket = 5 賣票:ticket = 3 賣票:ticket = 2 賣票:ticket = 1 賣票:ticket = 4
因為網絡操作可能會有延遲,所以這里增加一個延遲操作sleep();修改如下;
package Thread1; class MyThread implements Runnable{ private int ticket = 5 ; // 假設一共有5張票 public void run(){ for(int i=0;i<100;i++){ if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣票:ticket = " + ticket-- ); } } } }; public class demo1{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義線程對象 Thread t1 = new Thread(mt) ; // 定義Thread對象 Thread t2 = new Thread(mt) ; // 定義Thread對象 Thread t3 = new Thread(mt) ; // 定義Thread對象 t1.start() ; t2.start() ; t3.start() ; } };
運行結果:
賣票:ticket = 5 賣票:ticket = 3 賣票:ticket = 4 賣票:ticket = 2 賣票:ticket = 1 賣票:ticket = 0 賣票:ticket = -1
此時結果發現賣出的票數成負數,程序代碼出現問題。

例如,在線程2取出數據,但是還沒有減一送回去之前,線程n也取出了這個數據,也執行了減一操作,這樣這個數據同時執行了兩次減一操作,導致數據為負。
問題解決
要解決這種問題就要使用同步,所謂同步就是指多個操作在同一時間段內只能有一個線程進行,其他線程要等這個線程完成之后才能繼續執行。

2. 使用同步解決問題。
要想解決資源共享的同步操作問題,可以使用同步代碼塊及同步方法兩種方式完成。
2.1同步代碼塊
之前已經介紹過代碼塊分四種:
1)普通代碼塊,直接定義在方法中。
2)構造塊,是直接定義在類中,優先於構造方法執行,重復調用。
3)靜態塊,是使用static關鍵字聲明的,優先於構造塊執行,只執行一次。
4)同步代碼塊,使用synchronized關鍵字聲明的代碼塊,叫做同步代碼塊。
syschronized關鍵字相關知識:
java線程同步: synchronized詳解(轉)
同步代碼塊的格式:
synchronized(同步對象){ 需要同步的代碼。 }
同步的時候必須指明同步的對象,一般情況下,會將當前對象作為同步對象,使用this表示。
package Thread1; class MyThread implements Runnable{ private int ticket = 5 ; // 假設一共有5張票 public void run(){ for(int i=0;i<100;i++){ synchronized(this){ // 要對當前對象進行同步 if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣票:ticket = " + ticket-- ); } } } } }; public class demo1{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義線程對象 Thread t1 = new Thread(mt) ; // 定義Thread對象 Thread t2 = new Thread(mt) ; // 定義Thread對象 Thread t3 = new Thread(mt) ; // 定義Thread對象 t1.start() ; t2.start() ; t3.start() ; } };
運行結果:
賣票:ticket = 5 賣票:ticket = 4 賣票:ticket = 3 賣票:ticket = 2 賣票:ticket = 1
從運行結果可以發現,程序加入了同步操作,所以不會產生負數的情況,但是程序的執行效率明顯降低很多。
2.2同步方法
使用了synchronized聲明的方法為同步方法。
同步方法定義格式:
synchronized 方法返回值類型 方法名稱(參數列表) { }
同步方法解決如下:
package Thread1; class MyThread implements Runnable{ private int ticket = 5 ; // 假設一共有5張票 public void run(){ for(int i=0;i<100;i++){ this.sale() ; // 調用同步方法 } } public synchronized void sale(){ // 聲明同步方法 if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣票:ticket = " + ticket-- ); } } }; public class demo1{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義線程對象 Thread t1 = new Thread(mt) ; // 定義Thread對象 Thread t2 = new Thread(mt) ; // 定義Thread對象 Thread t3 = new Thread(mt) ; // 定義Thread對象 t1.start() ; t2.start() ; t3.start() ; } };
運行結果:
賣票:ticket = 5 賣票:ticket = 4 賣票:ticket = 3 賣票:ticket = 2 賣票:ticket = 1
3.3死鎖
資源共享時候需要進行同步操作。
程序中過多的同步會產生死鎖。

死鎖一般情況下就是表示互相等待,是在程序運行時候出現的一種文體。
下面通過一個代碼來模擬一下死鎖的概念,本代碼讀者只需了解其效果即可,對於代碼不必深入了解。
package Thread1; class Zhangsan{ // 定義張三類 public void say(){ System.out.println("張三對李四說:“你給我畫,我就把書給你。”") ; } public void get(){ System.out.println("張三得到畫了。") ; } }; class Lisi{ // 定義李四類 public void say(){ System.out.println("李四對張三說:“你給我書,我就把畫給你”") ; } public void get(){ System.out.println("李四得到書了。") ; } }; public class demo1 implements Runnable{ private static Zhangsan zs = new Zhangsan() ; // 實例化static型對象,保證兩個操作的對象是同一個,這樣syschronized(對象)才能起作用,能夠起同步的作用。 private static Lisi ls = new Lisi() ; // 實例化static型對象 private boolean flag = false ; // 聲明標志位,判斷那個先說話 public void run(){ // 覆寫run()方法 if(flag){ synchronized(zs){ // 同步張三,占用了zs對象。 zs.say() ; try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } synchronized(ls){ //因為ls對象已經被t2占用了,所以同步ls的時候被阻塞,所以在等待 zs.get() ; } } }else{ synchronized(ls){ //同步李四,占用了ls對象。 ls.say() ; try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } synchronized(zs){ //因為zs已經被t1占用了,所以同步zs的時候被阻塞,所以等待 ls.get() ; } } } } public static void main(String args[]){ demo1 t1 = new demo1() ; // 控制張三 demo1 t2 = new demo1() ; // 控制李四 t1.flag = true ; t2.flag = false ; Thread thA = new Thread(t1) ; Thread thB = new Thread(t2) ; thA.start() ; thB.start() ; } };
運行結果:
張三對李四說:“你給我畫,我就把書給你。”
李四對張三說:“你給我書,我就把畫給你”
此時雙方說完話都在等待對方回應,此時處於停滯狀態,都在互相等待着對方回答。
