9. 多線程 Part 3 同步與死鎖 --- 學習筆記



9.6 同步與死鎖

         一個多線程的程序如果是通過Runnabl接口實現的,則意味着類中的屬性將被多個線程共享,那麼這樣一來就會造成一種問題,如果這多個線程操作同一資源時就有可能出現資源的同步問題。例如, 賣票程序,如果多個線程同時操作時就有可能出現賣出票爲負數的問題。

      9.6.1 問題的產生

範例:通過Runnable接口實現多線程,併產生3個線程對象,同時賣5張票。

class MyThread implements Runnable{
    private int ticket = 5;
    public void run(){
        for (int i = 0; i < 7; i++){
            if (ticket > 0){
            	try{
            		Thread.sleep(3000);
        		}catch(InterruptedException e){
            		e.printStackTrace();
        		}
        		System.out.println("賣票: ticket = " + ticket--);
            }
        }
    }
}
public class SyncDemo01{
    public static void main(String args[]){
        MyThread my = new MyThread();
        new Thread(my, "Thread-A").start();
        new Thread(my, "Thread-B").start();
        new Thread(my, "Thread-C").start();
    }
}

運行結果截圖

賣票: ticket = 2
賣票: ticket = 1
賣票: ticket = 0
<span style="color:#ff0000;">賣票: ticket = -1</span>

-------------------------------------------------

     從程序的運行結果中可以發現,程序中加入了延遲操作,所以在運行的最後出現了負數的情況,那麼爲什麼現在會產生這樣的問題呢?

       上面程序對於票數的操作步驟如下:

  1. 判斷票數是否大於0,大於0則表示還有票可以賣
  2. 如果票數大於0,則將票賣出。

      但是,代碼中,在步驟1和步驟2之間加入了延遲操作,那麼一個線程就有可能在還沒有對票數進行減法操作之前,其他線程就已經將票數減少了,這樣一來就會出現票數爲負的情況。

             

     如果想解決這樣的問題,就必須使用同步。

        所謂同步就是指多個操作在同一個時間段內只能有一個線程進行,其他線程要等待此線程完成之後纔可以繼續執行,如下圖所示:

   


      9.6.2 使用同步解決問題

         解決資源共享的同步操作,可以使用同步代碼塊和同步方法兩種方式完成。

  •  同步代碼塊

所謂的代碼塊就是指使用“{}”括起來的一段代碼,根據其位置和聲明的不同,可以分爲普通代碼塊、構造塊、靜態塊3種,如果在代碼塊前面加上synchronized關鍵字,則此代碼塊就稱爲同步代碼塊。  其格式如下

synchronized(同步對象){
      需要同步的代碼;
}

*******從上面的格式可以看出,在使用同步代碼塊時必須制定一個同步對象,但一般都將當前對象(this)設置成同步對象!!!*******

範例:使用同步代碼塊解決的同步問題

class MyThread implements Runnable{
    private int ticket = 5;
    public void run(){
        for (int i = 0; i < 7; i++){
        	synchronized(this){
            	if (ticket > 0){
                	try{
                    	Thread.sleep(500);
                	}catch(InterruptedException e){
                    	e.printStackTrace();
                	}
                    System.out.println("賣票: ticket = " + ticket--);
            	}
        	}
        }
    }
}
public class SyncDemo01{
    public static void main(String args[]){
        MyThread my = new MyThread();
        new Thread(my, "Thread-A").start();
        new Thread(my, "Thread-B").start();
        new Thread(my, "Thread-C").start();
    }
}

運行結果:

-------------------------------------------------
賣票: ticket = 5
賣票: ticket = 4
賣票: ticket = 3
賣票: ticket = 2
賣票: ticket = 1
-------------------------------------------------

          將上面代碼的取值和修改值的操作代碼進行了同步,所以不會再出現賣出票爲負數的情況了。

  •  同步方法

               除了可以將需要的代碼設置成同步代碼塊外,也可以使用synchronized關鍵字將一個方法聲明成同步方法。其格式如下:

synchronized 方法返回值  方法名稱(參數列表){
    方法體;
}

範例: 使用同步方法解決賣票出負數的情況

class MyThread implements Runnable{
    private int ticket = 5;
    public void run(){
        for (int i = 0; i < 7; i++){
        	this.sale();
        }
    }
    private synchronized void sale(){
        if (ticket > 0){
            try{
                Thread.sleep(500);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("賣票:ticket = " + ticket--);
        }
    }
}
public class SyncDemo02{
    public static void main(String args[]){
        MyThread my = new MyThread();
        new Thread(my, "Thread-A").start();
        new Thread(my, "Thread-B").start();
        new Thread(my, "Thread-C").start();
    }
}

運行結果如下,與上面同步代碼塊實現了完全相同的功能。

-------------------------------------------------
賣票:ticket = 5
賣票:ticket = 4
賣票:ticket = 3
賣票:ticket = 2
賣票:ticket = 1
-------------------------------------------------

  • 方法定義的完整格式

訪問權限  {public|default|protected|private} [final] [static] [synchronized] 返回值類型|void  方法名稱(參數類型 參數名稱,…) [throws Exception1, Exception2] {

      [return [返回值|返回調用處]]

}

      9.6.3 死鎖

           同步可以保證資源共享操作的正確性,但是過多同步也會產生問題。例如, 現在張三想要李四的畫,李四想要張三的書;張三對李四說“把你的畫給我,我就給你書”,李四也對張三說了:“把你的書給我,我就給你畫”,此時,張三在等着李四的答覆,而李四也在等着張三的答覆;那麼這樣下去最終結果就是,張三得不到李四的畫,李四也得不到張三的書。這就是死鎖的概念!!

          所謂死鎖就是指兩個線程都在等待彼此先完成,造成了程序的停滯,一般程序的死鎖都是在程序運行時出現的。

範例:死鎖

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 ThreadDeadLock implements Runnable{
    private static Zhangsan zs = new Zhangsan();
    private static Lisi ls = new Lisi();
    private boolean flag = false;   //標記,用於判斷哪個對象先執行
    
    public void run(){
        if(flag){
            synchronized (zs){
                zs.say();
                try{
                    Thread.sleep(500);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                synchronized(ls){
                    zs.get();
                }
            }
        }else{
            synchronized(ls){
                ls.say();
                try{
                    Thread.sleep(500);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                synchronized(zs){
                    ls.get();
                }
            }
        }
    }
    public static void main(String args[]){
        ThreadDeadLock t1 = new ThreadDeadLock();
        ThreadDeadLock t2 = new ThreadDeadLock();
        t1.flag = true;
        t2.flag = false;
        new Thread(t1, "Thread-A").start();
        new Thread(t2, "Thread-B").start();
    }
}


程序運行結果:

-------------------------------------------------
 張三對李四說:“你給我畫,我就把書給你。”
李四對張三說:“你給我書,我就把畫給你。”

[以下代碼不再執行,程序進入死鎖狀態]
    從程序的運行結果中可以發現,兩個線程都在彼此等着對方的執行完成,這樣,程序就無法向下繼續執行,從而造成了死鎖的現象。

  • 關於同步與死鎖, 多個線程共享同一資源時需要進行同步,以保證資源操作的完整性;但是過多的同步就有可能產生死鎖。。。


關於線程的死鎖、互鎖、互斥鎖, 以及它們產生的原因、排錯方法等等要自己查資料看看!!!!!!!!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章