實現死鎖的兩種方式以及ReentrantLock的額外功能(未完待續)

思路:

  1. 死鎖是指在多線程環境下的這麼一種場景,兩個(多個)線程在分別拿到本身的鎖時嘗試獲取對方的鎖,因爲必須等待對方釋放鎖才能獲取,然而雙方誰也不願先釋放本身的鎖, 致使雙方誰都沒法繼續執行。java

  2. 經過一個實現runnable接口的類實例做爲兩個線程的執行對象,在該類中有兩個Object的靜態變量做爲鎖.經過該類的一個開關變量實如今同一個run方法中執行兩段不一樣的邏輯,一個先獲取鎖1, 再獲取鎖2,另外一個分支則恰好相反。多線程

  3. 爲了使第一個執行的線程在拿到第二個鎖以前失去cpu執行權,方便構造死鎖場景,在嘗試獲取第二個鎖以前,讓線程休眠一段時間,由於sleep()方法不會釋放鎖。ide

實現死鎖的方法有兩種,一種是使用synchronized同步代碼塊,另外一種是使用reentrantlock重入鎖。測試

使用同步代碼塊實現死鎖

代碼

public class TestDeadLock implements Runnable {
    //開關
    private boolean       flag;
    //鎖1
    private static Object lock1 = new Object();
    //鎖2
    private static Object lock2 = new Object();

    public TestDeadLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (lock1) {
                System.out.println(flag + "線程拿到了lock1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println(flag + "線程拿到了lock2");
                }
            }
        } else {
            synchronized (lock2) {
                System.out.println(flag + "線程拿到了lock2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println(flag + "線程拿到了lock1");
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new TestDeadLock(true));
        Thread thread2 = new Thread(new TestDeadLock(false));
        thread1.start();
        thread2.start();
    }
}

運行結果

true線程拿到了lock1
false線程拿到了lock2

使用ReentrantLock實現死鎖

代碼

public class TestDeadLock2 implements Runnable{
    private boolean flag;
    private static ReentrantLock lock1=new ReentrantLock();
    private static ReentrantLock lock2=new ReentrantLock();

    public TestDeadLock2(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            if(flag){
                lock1.lock();
                System.out.println(flag + "線程獲取了Lock1");
                TimeUnit.SECONDS.sleep(1);
                lock2.lock();
                System.out.println(flag+"線程獲取了Lock2");
            }else{
                lock2.lock();
                System.out.println(flag + "線程獲取了Lock2");
                TimeUnit.SECONDS.sleep(1);
                lock1.lock();
                System.out.println(flag+"線程獲取了Lock1");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(lock1.isHeldByCurrentThread()){
                lock1.unlock();
            }
            if(lock2.isHeldByCurrentThread()){
                lock2.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(new TestDeadLock2(true));
        Thread thread2=new Thread(new TestDeadLock2(false));
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("主線程已結束");
    }
}

運行結果

false線程獲取了Lock2
true線程獲取了Lock1

ReentrantLock和Synchronized的區別,具體可見

Java中的ReentrantLock和synchronized兩種鎖定機制的對比ui

總的來講,ReentrantLock所提供的功能比Synchronized要豐富的多,好比this

ReentrantLock的額外功能

lockInterruptibly

API簽名

public void lockInterruptibly() throws InterruptedExceptionspa

代碼

public class TestDeadLock3 implements Runnable {
    private boolean      flag;
    static ReentrantLock lock1 = new ReentrantLock();
    static ReentrantLock lock2 = new ReentrantLock();

    public TestDeadLock3(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {

        try {
            if (flag) {
                //可中斷地加鎖
                lock1.lockInterruptibly();
                System.out.println(flag + "線程獲取了lock1");
                TimeUnit.SECONDS.sleep(1);
                lock2.lockInterruptibly();
                System.out.println(flag + "線程獲取了lock2");
            } else {
                lock2.lockInterruptibly();
                System.out.println(flag + "線程獲取lock2");
                TimeUnit.SECONDS.sleep(1);
                lock1.lockInterruptibly();
                System.out.println(flag + "線程獲取了lock1");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock1.isHeldByCurrentThread()) {
                lock1.unlock();
                System.out.println(flag + "線程釋放lock1鎖");
            }
            if (lock2.isHeldByCurrentThread()) {
                lock2.unlock();
                System.out.println(flag + "線程釋放lock2鎖");
            }
            System.out.println(flag + "線程已退出");
        }

    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new TestDeadLock3(true));
        Thread thread2 = new Thread(new TestDeadLock3(false));
        thread1.start();
        thread2.start();
        //主線程休眠5秒
        TimeUnit.SECONDS.sleep(5);
        thread1.interrupt();
    }
}

運行結果

true線程獲取了lock1
false線程獲取lock2
true線程釋放lock1鎖
java.lang.InterruptedException
false線程獲取了lock1
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
true線程已退出
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
false線程釋放lock1鎖
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
false線程釋放lock2鎖
	at com.akane.test.reentrantlock.TestDeadLock3.run(TestDeadLock3.java:31)
false線程已退出
	at java.lang.Thread.run(Thread.java:744)

Process finished with exit code 0

關於interrupt的用法

synchronized在獲鎖的過程當中是不能被中斷的,意思是說若是產生了死鎖,則不可能被中斷(請參考後面的測試例子)。與synchronized功能類似的reentrantLock.lock()方法也是同樣,它也不可中斷的,即若是發生死鎖,那麼reentrantLock.lock()方法沒法終止,若是調用時被阻塞,則它一直阻塞到它獲取到鎖爲止。可是若是調用帶超時的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那麼若是線程在等待時被中斷,將拋出一個InterruptedException異常,這是一個很是有用的特性,由於它容許程序打破死鎖。你也能夠調用reentrantLock.lockInterruptibly()方法,它就至關於一個超時設爲無限的tryLock方法.net

主線程對Thread1進行了中斷,thread1拋出異常,異常被捕獲,在finally中釋放thread1得到的鎖,線程2得到須要的鎖,該線程得以繼續執行,死鎖就被解決了線程

tryLock

固然,ReentrantLock還提供了另一個更好的方法解決死鎖問題,那就是使用tryLock()方法,該方法會嘗試得到鎖,若是成功,返回true,失敗則返回false。該方法不等待或等待一段時間就返回code

API簽名

public boolean tryLock() 當即返回
public boolean tryLock(long timeout, TimeUnit unit) 等待一段時間後返回

死鎖的緣由在於吃着碗裏的看着鍋裏的,咱們讓線程拿到一個鎖以後不管是否拿到第二個鎖,都釋放已經拿到的鎖,能夠將此邏輯放入finally中,配合外層的while(true)屢次重複嘗試,若是成功獲取兩個鎖,則釋放兩個鎖的同時推出while循環,如下是代碼實現,線程睡眠時間由1秒改成1毫秒,減小測試須要的時間

代碼

public class TestDeadLock4 implements Runnable{
    private boolean      flag;
    static ReentrantLock lock1 = new ReentrantLock();
    static ReentrantLock lock2 = new ReentrantLock();
    //統計發生死鎖的次數
    private static int count;

    public TestDeadLock4(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            while (true) {
                if(lock1.tryLock()){
                    System.out.println(flag+"線程得到了lock1");
                    try {
                        TimeUnit.MILLISECONDS.sleep(1);
                        try {
                            if(lock2.tryLock()){
                                System.out.println(flag+"得到了lock2");
                            }
                        } finally {
                            //同時得到Lock1和lock2,沒有發生死鎖,任務完成,退出循環
                            if(lock1.isHeldByCurrentThread()&&lock2.isHeldByCurrentThread()){
                                System.out.println(flag+"線程執行完畢"+"---------------------");
                                lock1.unlock();
                                lock2.unlock();
                                break;
                            }else{
                                //說明發生了死鎖,只須要釋放lock1
                                //統計變量也要注意多線程問題
                                synchronized (TestDeadLock4.class) {
                                    count++;
                                    System.out.println("發生了"+count+"次死鎖");
                                }
                                lock1.unlock();
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }else{
            while (true) {
                if(lock2.tryLock()){
                    System.out.println(flag+"線程得到了lock2");
                    try {
                        TimeUnit.MILLISECONDS.sleep(1);
                        try {
                            if(lock1.tryLock()){
                                System.out.println(flag+"線程得到了lock1");
                            }
                        } finally {
                            if(lock1.isHeldByCurrentThread()&&lock2.isHeldByCurrentThread()){
                                System.out.println(flag+"線程執行完畢"+"---------------------");
                                lock1.unlock();
                                lock2.unlock();
                                break;
                            }else{
                                synchronized (TestDeadLock4.class) {
                                    count++;
                                    System.out.println("發生了"+count+"次死鎖");
                                }
                                lock2.unlock();
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new TestDeadLock4(true));
        Thread thread2 = new Thread(new TestDeadLock4(false));
        thread1.start();
        thread2.start();
    }
}

運行結果(部分)

發生了3335次死鎖
false線程得到了lock2
發生了3336次死鎖
true線程得到了lock1
發生了3337次死鎖
false線程得到了lock2
發生了3338次死鎖
true線程得到了lock1
發生了3339次死鎖
false線程得到了lock2
發生了3340次死鎖
true線程得到了lock1
發生了3341次死鎖
true得到了lock2
true線程執行完畢---------------------
false線程得到了lock2
false線程得到了lock1
false線程執行完畢---------------------

Process finished with exit code 0

公平鎖

除此以外,ReentrantLock還有能實現線程公平獲取鎖的功能,所謂的公平,指的是在申請獲取鎖的隊列中,排在前面的線程老是優先得到須要的鎖,Synchronized同步得到鎖的方式是非公平的,舉個例子,線程A和B都嘗試得到C持有的鎖,當C釋放該鎖時,A和B誰能得到該鎖是不肯定的,也就是非公平的,而ReentrantLock提供公平地,即先來後到地獲取鎖的方式。

相關文章
相關標籤/搜索