線程的同步與死鎖


在多線程中,同步與死鎖概念很重要,在本章中必須了解以下幾點:

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() ;
    }
};

運行結果:

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

此時雙方說完話都在等待對方回應,此時處於停滯狀態,都在互相等待着對方回答。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM