Android 8.1 源碼_線程篇 -- 淺析多線程中的 “同步” 和 “死鎖” 問題

Thread - 同步

問題引出

我們現在來通過Runnable接口實現多線程,產生3個線程對象,模擬賣票的場景!

class MyThread implements Runnable {
    private int ticket = 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 SyncDemo01 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);    
        t1.start();
        t2.start();
        t3.start();
    }
}

我們執行下這段代碼,結果如下:

賣票:ticket = 5
賣票:ticket = 4
賣票:ticket = 5
賣票:ticket = 3
賣票:ticket = 2
賣票:ticket = 2
賣票:ticket = 1
賣票:ticket = 0
賣票:ticket = -1    // 票數竟然還能負數?

爲什麼會出現“負數”的情況:在上面的操作中,我們可以發現,因爲加入了“延遲操作”一個線程很有可能在還沒對票數進行減操作之前,其他線程就已經將票數減少了,這樣就會出現票數爲負的情況。

有沒有方法解決?肯定是有的!想解決這樣的問題,就必須使用同步!所謂同步,就是指多個操作在同一個時間段內只能有一個線程進行,其他線程要等待此線程完成之後纔可以繼續執行。

解決問題

解決資源共享的同步操作,有兩種方法:同步代碼塊同步方法

同步代碼塊

所謂代碼塊就是指使用“{}”括起來的一段代碼,如果在代碼塊上加上synchronized關鍵字,則此代碼塊就成爲同步代碼塊。

【同步代碼塊 - 格式】

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

我們對代碼進行修改:

class MyThread implements Runnable {
    private int ticket = 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 SyncDemo01 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);    
        t1.start();
        t2.start();
        t3.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 < 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 SyncDemo01 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);    
        t1.start();
        t2.start();
        t3.start();
    }
}

我們重新執行下這段代碼,結果如下:

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

從以上程序的運行結果可以發現,此代碼完成了與之前同步代碼塊同樣的功能。

總結

多個線程共享同一資源時需要進行同步,以保證資源操作的完整性。

Thread - 死鎖

通過上面的例子,我們發現,同步還是很有好處的,它可以保證資源共享操作的正確性,但是過多的同步也會產生問題,這就是我們接下來要討論“死鎖”問題!

什麼是死鎖?

多線程以及多進程改善了系統資源的利用率並提高了系統 的處理能力。然而,併發執行也帶來了新的問題 -- 死鎖

在編寫多線程的時候,必須要注意資源的使用問題,如果兩個或多個線程分別擁有不同的資源,而同時又需要對方釋放資源才能繼續運行時,就會發生死鎖。

簡單來說:死鎖就是當一個或多個進程都在等待系統資源,而資源本身又被佔用時,所產生的一種狀態。

造成死鎖的原因

多個線程競爭共享資源,由於資源被佔用,資源不足或進程推進順序不當等原因造成線程處於永久阻塞狀態,從而引發死鎖。

造成死鎖的四個條件

1、互斥條件:進程對於所分配到的資源具有排它性,即一個資源只能被一個進程佔用,直到被該進程釋放
2、請求和保持條件:一個進程因請求被佔用資源而發生阻塞時,對已獲得的資源保持不放。
3、不剝奪條件:任何一個資源在沒被該進程釋放之前,任何其他進程都無法對他剝奪佔用
4、循環等待條件:當發生死鎖時,所等待的進程必定會形成一個環路(類似於死循環),造成永久阻塞。

問題引出

現在張三想要李四的畫,李四想要張三的書,於是產生了以下對話:

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

此時,張山在等着李四的答覆,李四也在等着張三的答覆,那麼這樣下去的結果就是,兩個人都在等待,但是都沒有結果,這就是“死鎖”!

從線程角度來說,所謂死鎖就是指兩個線程都在等待彼此先完成,造成了程序的停滯,一般程序的死鎖都是在程序運行時出現,比如我們通過一個代碼範例來看看發生死鎖的場景。

class Zhangsan {
    public void say() {
        System.out.println("Zhangsan say: give me your painting, i will give you my book!");
    }
    
    public void get() {
        System.out.println("Zhangsan got Lisi's painting!");
    }
}

class Lisi {
    public void say() {
        System.out.println("Lisi say: give me your book, i will give you my painting!");
    }
    
    public void get() {
        System.out.println("Lisi got Zhangsan's book!");
    }
}

public class ThreadDeadLock implements Runnable {
    private static Zhangsan zs = new Zhangsan();         // 實例化static型對象,數據共享
    
    private static Lisi ls = new Lisi();                 // 實例化static型對象,數據共享
    
    private boolean flag = false;                        // 聲明標記,用於判斷哪個對象先執行
    
    public void run() {
        if (flag) {                                      // 判斷標誌位,flag爲true,Zhangsan先執行
            synchronized (zs) {                          // 同步第一個對象
                zs.say();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (ls) {                      // 同步第二個對象
                    zs.get();
                }
            }
        } else {                                         // Lisi先執行
            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;
        Thread thA = new Thread(t1);
        Thread thB = new Thread(t2);
        thA.start();
        thB.start();
    }
}

我們執行下這段代碼,結果如下:

Zhangsan say: give me your painting, i will give you my book!
Lisi say: give me your book, i will give you my painting!

從程序的運行結果中可以看出,兩個線程都在彼此等待着對方的執行完成,這樣,程序就無法向下繼續執行,從而造成了死鎖的現象。

解決問題

要預防和避免死鎖的發生,只需將上面所講到的4個條件破壞掉其中之一即可。

如上面的代碼當中,有四個同步代碼塊,只需要將其中一個同步代碼塊去掉,即可解決死鎖問題,一般而言破壞“循環等待”這個條件是解決死鎖最有效的方法。

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