線程的死鎖與避免死鎖策略

一、兩種程序運調度方式

1、分時調度
所有線程輪流使用 CPU 的使用權,平均分配每個線程佔用 CPU 的時間。
2、搶佔式調度
優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度。

注:多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,使得CPU的利用率更高。

爲了保證多線程安全,我們通常會採用加同步鎖的方式:
同步方法:在方法聲明上加上synchronized

   public synchronized void method(){
        可能會產生線程安全問題的代碼
    }

靜態同步方法: 在方法聲明上加上static synchronized

public static synchronized void method(){
		可能會產生線程安全問題的代碼
}

同步代碼塊:在需要同步的代碼外面包上一個synchronized,同步代碼塊中的鎖對象可以是任意對象,通常會寫成this(即調用者對象)

synchronized(Object o){
   		 可能會產生線程安全問題的代碼塊
} 

同步鎖使用的弊端,即引出今天的主要話題:死鎖

二、死鎖產生的四個充分必要條件:

1.互斥條件:一個資源每次只能被一個進程佔用。
2.不剝奪條件:線程已獲得的資源在末使用完之前不能被其他線程強行剝奪,只有自己使用完畢後才釋放資源。
3.請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
4.循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係

產生死鎖必須同時滿足以下四個條件,只要其中任一條件不成立,死鎖就不會發生。

三、產生死鎖的一個例子

如下圖所示,線程 A 持有資源 2,線程 B 持有資源 1,他們同時都想申請對方的資源,所以這兩個線程就會互相等待而進入死鎖狀態。
在這裏插入圖片描述

具體代碼

  package com.weiwei;  
    /**
     * Created by Moody丶Yang
     * Date:2019/4/20
     */
    public class DeadLock {
    
        private static Object resource1=new Object();
        private static Object resource2=new Object();
    
        public static void main(String[] args) {
            
            new Thread(() -> {
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get resource2");
                    synchronized (resource2) {
                        System.out.println(Thread.currentThread() + "get resource2");
                    }
                }
            }, "線程 1").start();
    
            new Thread(() -> {
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get resource1");
                    synchronized (resource1) {
                        System.out.println(Thread.currentThread() + "get resource1");
                    }
                }
            }, "線程 2").start();
        }
    }

輸出:

在這裏插入圖片描述

四、避免死鎖的策略

由上述死鎖產生是需要四個充分必要條件的可知, 只要破壞四個條件中的其中一個就可以避免死鎖了。

1、 加鎖順序(線程按照一定的順序加鎖,當多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生.) 簡單、易行!
2、 加鎖時限(當線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己佔有的鎖)
3、死鎖檢測(給線程設置隨機優先級,發生死鎖時,讓一個(或幾個)線程回退,剩下的線程就像沒發生死鎖一樣繼續保持着它們需要的鎖。)

運用法一解決上述死鎖問題(將線程 2加鎖順序與線程1的一致 )

        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "線程 2").start();

輸出:

在這裏插入圖片描述

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