Java多執行緒——synchronized的使用示例

SachinLea發表於2019-03-04

  synchronized是我們常用的鎖,synchronized鎖是互斥的,同一時間只能有一個執行緒獲得鎖,因此能夠保證執行緒安全;synchronized又是可重入的鎖。 synchronized常用範圍:

  • 修飾普通同步方法,鎖定當前物件;
  • 也可以修飾靜態方法,鎖定的是當前類的class物件;
  • 還可以修改程式碼塊,鎖定括號中的內容。

1. synchronized同步普通方法

1.1 應用示例

  執行緒安全問題最常見就是物件的成員變數的計算問題,先看在沒有synchronized的情況下,我們的計算會出現什麼問題。

@Slf4j
@Getter
public class HasSelfNum implements Runnable {
    private int num = 0;

    @Override
    public void run() {
        add();
    }

    public void add() {
        for (int i = 0; i < 10; i++) {
            num++;
            log.info("當前執行緒:{},num = {}", Thread.currentThread().getName(), num);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
複製程式碼

測試方法:

@Slf4j
public class Main {

    public static void main(String[] args) throws Exception {
        HasSelfNum hasSelfNum = new HasSelfNum();
        Thread threadA = new Thread(hasSelfNum, "執行緒A");
        Thread threadB = new Thread(hasSelfNum, "執行緒B");
        threadA.start();
        threadB.start();
        threadA.join();
        threadB.join();
        log.info("最終num = {}", hasSelfNum.getNum());
    }
}
複製程式碼

輸出結果:

15:20:49.075 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 2
15:20:49.075 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 1
15:20:50.080 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 3
15:20:50.080 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 3
15:20:51.081 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 5
15:20:51.081 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 5
15:20:52.082 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 6
15:20:52.082 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 6
15:20:53.082 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 7
15:20:53.082 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 7
15:20:54.083 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 8
15:20:54.083 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 9
15:20:55.084 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 10
15:20:55.084 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 10
15:20:56.085 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 12
15:20:56.085 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 12
15:20:57.086 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 14
15:20:57.086 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 13
15:20:58.086 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 15
15:20:58.087 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 16
15:20:59.088 [main] INFO com.sachin.threadlearn.sync.sync1.Main - 最終num = 16
複製程式碼

  根據上邊的實驗,在不使用synchronized的情況下,多個執行緒可能會同時得到相同的資料,在後邊的計算中造成錯誤,多執行幾次,每次計算的結果都會有所不同,但是大部分都是不正確的。為了保證當前執行緒能夠獲取到正確的資料,我們引入synchronized鎖,每次只能有一個執行緒獲取資料計算:

public synchronized void add() {
    for (int i = 0; i < 10; i++) {
        num++;
        log.info("當前執行緒:{},num = {}", Thread.currentThread().getName(), num);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

測試程式計算結果:

15:25:35.115 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 1
15:25:36.120 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 2
15:25:37.121 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 3
15:25:38.122 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 4
15:25:39.122 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 5
15:25:40.123 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 6
15:25:41.124 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 7
15:25:42.125 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 8
15:25:43.125 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 9
15:25:44.126 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 10
15:25:45.127 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 11
15:25:46.128 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 12
15:25:47.129 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 13
15:25:48.130 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 14
15:25:49.131 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 15
15:25:50.131 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 16
15:25:51.132 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 17
15:25:52.133 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 18
15:25:53.133 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 19
15:25:54.134 [執行緒B] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒B,num = 20
15:25:55.136 [main] INFO com.sachin.threadlearn.sync.sync1.Main - 最終num = 20
複製程式碼

  根據測試結果,可以看到,synchronized鎖保證了執行緒順序執行,最後結果也能保證正確。關於上邊測試程式碼中的join()方法,其主要作用是,保證主執行緒等待子執行緒銷燬之後再繼續執行,避免子執行緒還沒結束,主執行緒就輸出了結果,造成輸出結果錯誤。

1.2 驗證鎖定當前物件

  最開始,我們說過synchronized修飾普通方法時,鎖定的是當前物件,為了驗證這個觀點,我們建立一個類中有兩個同步方法,然後建立兩個執行緒,分別呼叫不同的方法:

@Slf4j
public class MyService {

    public synchronized void methodA() {
        try {
            log.info("當前執行緒:{}, 開始呼叫方法A", Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(5);
            log.info("當前執行緒:{}, 離開方法A", Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void methodB() {
        try {
            log.info("當前執行緒:{}, 開始呼叫方法B", Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(5);
            log.info("當前執行緒:{}, 離開方法B", Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

@AllArgsConstructor
public class ThreadA extends Thread {

    private MyService service;

    @Override
    public void run() {
        service.methodA();
    }
}

@AllArgsConstructor
public class ThreadB extends Thread {

    private MyService service;

    @Override
    public void run() {
        service.methodB();
    }
}
複製程式碼

測試程式:

public static void main(String[] args) {
    MyService service = new MyService();
    ThreadA threadA = new ThreadA(service);
    ThreadB threadB = new ThreadB(service);
    threadA.start();
    threadB.start();
}
複製程式碼

測試結果:

15:40:19.011 [Thread-0] INFO com.sachin.threadlearn.sync.lockInstance.MyService - 當前執行緒:Thread-0, 開始呼叫方法A
15:40:24.015 [Thread-0] INFO com.sachin.threadlearn.sync.lockInstance.MyService - 當前執行緒:Thread-0, 離開方法A
15:40:24.016 [Thread-1] INFO com.sachin.threadlearn.sync.lockInstance.MyService - 當前執行緒:Thread-1, 開始呼叫方法B
15:40:29.017 [Thread-1] INFO com.sachin.threadlearn.sync.lockInstance.MyService - 當前執行緒:Thread-1, 離開方法B
複製程式碼

  可以看到,另一個執行緒必須等第一個執行緒釋放鎖之後,才能獲得鎖,執行程式,說明他們獲取的同一個鎖,都是service物件。
  如果兩個執行緒,構造方法中是不同的物件,那麼他們就會獲取不同的鎖,也就不會等待了,修改測試方法驗證:

public static void main(String[] args) {
   MyService service = new MyService();
    ThreadA threadA = new ThreadA(service);
    ThreadB threadB = new ThreadB(new MyService());
    threadA.start();
    threadB.start();
}
複製程式碼

測試結果:

15:44:07.772 [Thread-1] INFO com.sachin.threadlearn.sync.lockInstance.MyService - 當前執行緒:Thread-1, 開始呼叫方法B
15:44:07.772 [Thread-0] INFO com.sachin.threadlearn.sync.lockInstance.MyService - 當前執行緒:Thread-0, 開始呼叫方法A
15:44:12.776 [Thread-0] INFO com.sachin.threadlearn.sync.lockInstance.MyService - 當前執行緒:Thread-0, 離開方法A
15:44:12.776 [Thread-1] INFO com.sachin.threadlearn.sync.lockInstance.MyService - 當前執行緒:Thread-1, 離開方法B
複製程式碼

  根據以上一個例子,可以看到synchronized作用在普通例項方法上時,鎖定的是當前物件。

1.3 可重入驗證

  之前我們說synchronized鎖是可重入鎖,可重入鎖就是說,一個執行緒得到一個物件鎖後,再次請求此物件鎖時是可以再次得到該物件的鎖的;也就是說在一個synchronized方法內部呼叫另一個synchronized方法,是永遠能夠得到鎖的。下邊我們用程式驗證:

@Slf4j
@Getter
public class HasSelfNum implements Runnable {
    private int num = 0;

    @Override
    public void run() {
        add();
    }

    public synchronized void add() {
        for (int i = 0; i < 10; i++) {
            num++;
            printLog();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void printLog() {
        log.info("當前執行緒:{},num = {}", Thread.currentThread().getName(), num);
    }
}
複製程式碼

  將之前計算的方法拆成兩個,根據上邊鎖定物件的驗證,如果是兩個執行緒分別呼叫者兩個方法是需要等待了,但是如果是同一個執行緒已經獲得鎖還未釋放,去呼叫另一個方法,將不用再次去請求鎖,能夠直接呼叫。
測試方法:

HasSelfNum hasSelfNum = new HasSelfNum();
Thread threadA = new Thread(hasSelfNum, "執行緒A");
threadA.start();
複製程式碼

測試結果:

15:47:00.249 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 1
15:47:01.253 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 2
15:47:02.253 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 3
15:47:03.253 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 4
15:47:04.254 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 5
15:47:05.254 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 6
15:47:06.255 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 7
15:47:07.256 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 8
15:47:08.257 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 9
15:47:09.258 [執行緒A] INFO com.sachin.threadlearn.sync.sync1.HasSelfNum - 當前執行緒:執行緒A,num = 10
複製程式碼

  可以看到,同一個執行緒獲得鎖之後,在一個同步方法中,呼叫另一個同步方法,是永遠能夠成功的。

2. synchronized同步靜態方法

  synchronized修飾靜態方法時,鎖定的是class物件,下邊我們通過程式,驗證該觀點,建立一個類,分別有一個synchronized修飾的靜態方法和synchronized修飾的普通方法,然後建立兩個執行緒分別呼叫不同的方法:

@Slf4j
public class MyService {

    public synchronized static void methodA() {
        try {
            log.info("開始執行靜態方法A");
            TimeUnit.SECONDS.sleep(5);
            log.info("離開靜態方法A");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void methodB() {
        try {
            log.info("開始執行例項方法B");
            TimeUnit.SECONDS.sleep(5);
            log.info("離開例項方法B");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

@AllArgsConstructor
public class ThreadA extends Thread {

    private MyService service;

    @Override
    public void run() {
        service.methodA();
    }
}
@AllArgsConstructor
public class ThreadB extends Thread {

    private MyService service;

    @Override
    public void run() {
        service.methodB();
    }
}
複製程式碼

測試程式:

public static void main(String[] args) {
    MyService service = new MyService();
    ThreadA threadA = new ThreadA(service);
    ThreadB threadB = new ThreadB(service);
    threadA.start();
    threadB.start();
}
複製程式碼

測試結果:

15:20:55.192 [Thread-1] INFO com.sachin.threadlearn.sync.sync2.MyService - 開始執行例項方法B
15:20:55.192 [Thread-0] INFO com.sachin.threadlearn.sync.sync2.MyService - 開始執行靜態方法A
15:21:00.197 [Thread-1] INFO com.sachin.threadlearn.sync.sync2.MyService - 離開例項方法B
15:21:00.197 [Thread-0] INFO com.sachin.threadlearn.sync.sync2.MyService - 離開靜態方法A
複製程式碼

  兩個執行緒幾乎同時開始執行相應的方法,說明兩個執行緒獲得鎖不是同一個鎖。

3. synchronized修飾程式碼塊

  在上邊的例子中synchronized都是修飾的整個方法,在這種情況下,每個執行緒呼叫該方法時,都得等待獲得鎖的執行緒釋放鎖之後才可以執行,但是,某些情況下,方法中只有部分需要使用鎖,其他的即使多執行緒呼叫也不會存在問題,這種情況下,我們只需要將需要鎖定的程式碼塊用synchronized修飾就可以了,下邊看一下具體的使用:

@Slf4j
public class MyService implements Runnable {

    private int num;

    @Override
    public void run() {
        add();
    }
    private void add() {
        log.info("執行緒:{},進入方法中", Thread.currentThread().getName());
        synchronized (this) {
            log.info("執行緒:{},進入同步程式碼塊", Thread.currentThread().getName());
            for (int i = 0; i < 5; i++) {
                num++;
            }
            log.info("執行緒:{},離開同步程式碼塊", Thread.currentThread().getName());
        }
        log.info("執行緒:{},離開方法", Thread.currentThread().getName());
    }
}
複製程式碼

測試程式碼:

public static void main(String[] args) {
    MyService service = new MyService();
    Thread thread1 = new Thread(service);
    Thread thread2 = new Thread(service);
    thread1.start();
    thread2.start();
}
複製程式碼

執行結果:

16:42:22.333 [Thread-0] INFO com.sachin.threadlearn.sync.syncCode.MyService - 執行緒:Thread-0,進入方法中
16:42:22.333 [Thread-1] INFO com.sachin.threadlearn.sync.syncCode.MyService - 執行緒:Thread-1,進入方法中
16:42:22.336 [Thread-0] INFO com.sachin.threadlearn.sync.syncCode.MyService - 執行緒:Thread-0,進入同步程式碼塊
16:42:22.336 [Thread-0] INFO com.sachin.threadlearn.sync.syncCode.MyService - 執行緒:Thread-0,離開同步程式碼塊
16:42:22.336 [Thread-0] INFO com.sachin.threadlearn.sync.syncCode.MyService - 執行緒:Thread-0,離開方法
16:42:22.336 [Thread-1] INFO com.sachin.threadlearn.sync.syncCode.MyService - 執行緒:Thread-1,進入同步程式碼塊
16:42:22.336 [Thread-1] INFO com.sachin.threadlearn.sync.syncCode.MyService - 執行緒:Thread-1,離開同步程式碼塊
16:42:22.336 [Thread-1] INFO com.sachin.threadlearn.sync.syncCode.MyService - 執行緒:Thread-1,離開方法
複製程式碼

  根據執行結果可以看到,兩個執行緒幾乎同時進入方法中,但是,在synchronized程式碼塊,兩個執行緒依次執行。說明了,該部分程式碼被鎖定,只有獲取鎖才能獲得。
  上邊synchronized修飾普通方法和靜態方法時,分別鎖定了當前物件和class物件,那麼,修飾同步程式碼塊時,鎖定的時哪個部分呢?可以看到synchronized後邊有一個括號,裡邊寫了this,這種情況下,鎖定當前物件,也可以寫Myservice.class甚至其他的物件;
  下邊驗證鎖定當前的情況,建立一個類,其中有三個方法,分別使用synchronized鎖定程式碼塊,普通方法,靜態方法,然後,建立三個執行緒,分別呼叫三個方法:

@Slf4j
public class MyService {

    public void methodA() {
        synchronized (this) {
            try {
                log.info("執行緒:{},進入同步程式碼塊", Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(3);
                log.info("執行緒:{},離開同步程式碼塊", Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void methodB() {
        try {
            log.info("執行緒:{},進入同步方法", Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(3);
            log.info("執行緒:{},離開同步方法", Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void methodC() {
        try {
            log.info("執行緒:{},進入同步靜態方法", Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(3);
            log.info("執行緒:{},離開同步靜態方法", Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

呼叫執行緒:

@AllArgsConstructor
public class ThreadA implements Runnable {

    private MyService service;

    @Override
    public void run() {
        service.methodA();
    }
}
...... 省略其他兩個執行緒
複製程式碼

測試方法:

public class Main {
    public static void main(String[] args) {
        MyService service = new MyService();
        new Thread(new ThreadA(service)).start();
        new Thread(new ThreadB(service)).start();
        new Thread(new ThreadC(service)).start();
    }
}
複製程式碼

驗證結果:

22:03:24.832 [Thread-0] INFO com.sachin.threadlearn.sync.syncCode2.MyService - 執行緒:Thread-0,進入同步程式碼塊
22:03:24.832 [Thread-2] INFO com.sachin.threadlearn.sync.syncCode2.MyService - 執行緒:Thread-2,進入同步靜態方法
22:03:27.836 [Thread-2] INFO com.sachin.threadlearn.sync.syncCode2.MyService - 執行緒:Thread-2,離開同步靜態方法
22:03:27.836 [Thread-0] INFO com.sachin.threadlearn.sync.syncCode2.MyService - 執行緒:Thread-0,離開同步程式碼塊
22:03:27.837 [Thread-1] INFO com.sachin.threadlearn.sync.syncCode2.MyService - 執行緒:Thread-1,進入同步方法
22:03:30.838 [Thread-1] INFO com.sachin.threadlearn.sync.syncCode2.MyService - 執行緒:Thread-1,離開同步方法
複製程式碼

  根據上邊的程式碼可以看到,synchronized (this)鎖定的是當前物件,和修飾普通方法時相同;如果修改為synchronized (MyService.class),則鎖定的class物件;另外還可以鎖定任意的一個物件synchronized (object),這種情況下幾個執行緒能夠分別獲得鎖,因為每個執行緒的鎖都不同。

4. 其他場景

  在多執行緒的其他場景中,也伴隨著synchronized的出現,例如:執行緒之間的通訊(等待/通知機制),死鎖等等。這裡,我們先看看死鎖是怎麼回事。
  死鎖,是指不同的執行緒都在等待不可能釋放的鎖,從而導致所有的任務都無法繼續完成。下邊使用一個互相等待鎖的例子,來檢視死鎖的情況:

@Slf4j
@AllArgsConstructor
public class DeadThread implements Runnable {

    private String username;

    private Object lock1;
    private Object lock2;

    @Override
    public void run() {
        if (username.equals("a")) {
            methodA();
        } else {
            methodB();
        }
    }

    private void methodA() {
        synchronized (lock1) {
            try {
                TimeUnit.SECONDS.sleep(3);
                log.info("執行緒:{},已經獲得lock1等待獲取lock2", Thread.currentThread().getName());
                synchronized (lock2) {
                    log.info("執行緒:{},已經獲得lock1,並且獲取lock2", Thread.currentThread().getName());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
    private void methodB() {
        synchronized (lock2) {
            try {
                TimeUnit.SECONDS.sleep(3);
                log.info("執行緒:{},已經獲得lock2等待獲取lock1", Thread.currentThread().getName());
                synchronized (lock1) {
                    log.info("執行緒:{},已經獲得lock2,並且獲取lock1", Thread.currentThread().getName());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
複製程式碼

測試方法:

public static void main(String[] args) {
    Object lock1 = new Object();
    Object lock2 = new Object();
    new Thread(new DeadThread("a", lock1, lock2), "執行緒a").start();
    new Thread(new DeadThread("b", lock1, lock2), "執行緒b").start();
}
複製程式碼

  建立兩個執行緒,一個先獲取lock1鎖,然後,試圖獲取lock2鎖;另外,一個執行緒先獲取lock2鎖,然後,獲取lock1鎖;由於兩個執行緒基本同時啟動,在獲取另一個或之前sleep了3秒,也足夠另一個執行緒獲取鎖,也就是說,兩個執行緒都在等待獲取對方釋放鎖,顯然他們不能獲取對方的鎖的話,就不會釋放鎖,因此造成死鎖。

相關文章