基於ReentrantLock發生死鎖的解決方案

概念

死鎖

是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。 因爲資源佔用是互斥的,當某個進程提出申請資源後,使得有關進程在無外力協助下,永遠分配不到必需的資源而沒法繼續運行,這就產生了一種特殊現象:死鎖。」java

活鎖(英文 livelock)

指事物1可使用資源,但它讓其餘事物先使用資源;事物2可使用資源,但它也讓其餘事物先使用資源,因而二者一直謙讓,都沒法使用資源。數組

避免活鎖的簡單方法是採用先來先服務的策略。當多個事務請求封鎖同一數據對象時,封鎖子系統按請求封鎖的前後次序對事務排隊,數據對象上的鎖一旦釋放就批准申請隊列中第一個事務得到鎖。多線程

示例一

使用tryLock()方法來防止多線程死鎖。dom

tryLock()方法:嘗試獲取一把鎖,若是獲取成功返回true,若是還拿不到鎖,就返回false。ide

public class DeadLock1 {

    private static Lock lock1 = new ReentrantLock();
    private static Lock lock2 = new ReentrantLock();

    public static void deathLock() {
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    if (lock1.tryLock()) {
                        try {
                            //若是獲取成功則執行業務邏輯,若是獲取失敗,則釋放lock1的鎖,自旋從新嘗試得到鎖
                            if (lock2.tryLock()) {
                                try {
                                    System.out.println("Thread1:已成功獲取 lock1 and lock2 ...");
                                    break;
                                } finally {
                                    lock2.unlock();
                                }
                            }
                        } finally {
                            lock1.unlock();
                        }
                    }
                    System.out.println("Thread1:獲取鎖失敗,從新獲取---");
                    try {
                        //防止發生活鎖
                        TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                while (true) {
                    if (lock2.tryLock()) {
                        try {
                            //若是獲取成功則執行業務邏輯,若是獲取失敗,則釋放lock2的鎖,自旋從新嘗試得到鎖
                            if (lock1.tryLock()) {
                                try {
                                    System.out.println("Thread2:已成功獲取 lock2 and lock1 ...");
                                    break;
                                } finally {
                                    lock1.unlock();
                                }
                            }
                        } finally {
                            lock2.unlock();
                        }
                    }
                    System.out.println("Thread2:獲取鎖失敗,從新獲取---");
                    try {
                        //防止發生活鎖
                        TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            deathLock();
        }
    }
}

該示例啓動兩個線程。線程1首先獲取lock1的鎖,而後再獲取lock2的鎖;線程2首先獲取lock2的鎖,而後再獲取lock1的鎖。這樣若是這時線程1得到了lock1的鎖,同時線程2得到lock2的鎖,而後線程1嘗試去得到lock2的鎖,線程2嘗試得到線程1的鎖,就會形成死鎖。spa

咱們這裏使用tryLock來獲取兩個鎖,若是一個線程不能同時獲取兩把鎖,那麼就回退並自旋從新嘗試(使用while循環)。再使用TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));隨機休眠一段時間,從而下降發生活鎖的可能性。若是處理成功,則使用break跳出循環。線程

示例二

使用tryLock(long timeout, TimeUnit unit) 方法來防止多線程死鎖。日誌

tryLock(long time, TimeUnit unit)方法和tryLock()方法是相似的,只不過區別在於這個方法在拿不到鎖時會等待必定的時間,在時間期限以內若是還拿不到鎖,就返回false。若是一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。code

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadLock2 {

    private static ReentrantLock lock1 = new ReentrantLock();
    private static ReentrantLock lock2 = new ReentrantLock();

    public static void deathLock() {
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        if (lock1.tryLock(10, TimeUnit.MILLISECONDS)) {
                            try {
                                //若是獲取成功則執行業務邏輯,若是獲取失敗,則釋放lock1的鎖,自旋從新嘗試得到鎖
                                if (lock2.tryLock(10, TimeUnit.MILLISECONDS)) {
                                    System.out.println("Thread1:已成功獲取 lock1 and lock2 ...");
                                    break;
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            } finally {
                                if(lock2.isHeldByCurrentThread()){
                                    lock2.unlock();
                                }
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if(lock1.isHeldByCurrentThread()){
                            lock1.unlock();
                        }
                    }
                    System.out.println("Thread1:獲取鎖失敗,從新獲取---");
                    try {
                        TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        if (lock2.tryLock(10, TimeUnit.MILLISECONDS)) {
                            try {
                                //若是獲取成功則執行業務邏輯,若是獲取失敗,則釋放lock1的鎖,自旋從新嘗試得到鎖
                                if (lock1.tryLock(10, TimeUnit.MILLISECONDS)) {
                                    System.out.println("Thread2:已成功獲取 lock2 and lock1 ...");
                                    break;
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            } finally {
                                if(lock1.isHeldByCurrentThread()){
                                    lock1.unlock();
                                }
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        if(lock2.isHeldByCurrentThread()){
                            lock2.unlock();
                        }
                    }
                    System.out.println("Thread2:獲取鎖失敗,從新獲取---");
                    try {
                        TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            deathLock();
        }
    }
}

該示例同示例一。咱們這裏使用tryLock(long time, TimeUnit unit)來獲取兩個鎖,若是一個線程不能同時獲取兩把鎖,那麼就回退並自旋從新嘗試(使用while循環)。在使用TimeUnit.NANOSECONDS.sleep(new Random().nextInt(100));隨機休眠一段時間,從而下降發生活鎖的可能性。若是處理成功,則使用break跳出循環。對象

示例三

使用lockInterruptibly()得到鎖,若是發生死鎖,調用線程interrupt來消除死鎖。

ReentrantLock.lockInterruptibly容許在等待時由其它線程調用等待線程的Thread.interrupt方法來中斷等待線程的等待而直接返回,這時不用獲取鎖,而會拋出一個InterruptedException。而ReentrantLock.lock方法不容許Thread.interrupt中斷,即便檢測到Thread.isInterrupted,同樣會繼續嘗試獲取鎖,失敗則繼續休眠。只是在最後獲取鎖成功後再把當前線程置爲interrupted狀態。

public class DeadLock3 {

    private static Lock lock1 = new ReentrantLock();
    private static Lock lock2 = new ReentrantLock();

    public static void deathLock() {
        new Thread() {
            @Override
            public void run() {
                try {
                    lock1.lockInterruptibly();
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        lock2.lockInterruptibly();
                        System.out.println("thread 1 ...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock2.unlock();
                    }

                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                } finally {
                    lock1.unlock();
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                try {
                    lock2.lockInterruptibly();
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        lock1.lockInterruptibly();
                        System.out.println("thread 1 ...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock1.unlock();
                    }

                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                } finally {
                    lock2.unlock();
                }
            }
        }.start();
    }

    public static void main(String[] args) throws InterruptedException {
        deathLock();

        TimeUnit.SECONDS.sleep(2);
        checkDeadLock();
    }

    //基於JMX獲取線程信息
    public static void checkDeadLock() {
        //獲取Thread的MBean
        ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
        //查找發生死鎖的線程,返回線程id的數組
        long[] deadLockThreadIds = mbean.findDeadlockedThreads();
        System.out.println("---" + deadLockThreadIds);
        if (deadLockThreadIds != null) {
            //獲取發生死鎖的線程信息
            ThreadInfo[] threadInfos = mbean.getThreadInfo(deadLockThreadIds);
            //獲取JVM中全部的線程信息
            Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
            for (Entry<Thread, StackTraceElement[]> entry : map.entrySet()) {
                for (int i = 0; i < threadInfos.length; i++) {
                    Thread t = entry.getKey();
                    if (t.getId() == threadInfos[i].getThreadId()) {
                        //中斷髮生死鎖的線程
                        t.interrupt();
                        //打印堆棧信息       
                        // for (StackTraceElement ste : entry.getValue()) {
                        // // System.err.println("t" + ste.toString().trim());
                        // }
                    }

                }
            }
        }
    }
}

咱們這裏使用lockInterruptibly()方法來獲取鎖,咱們這裏使用線程1獲取lock1 休眠1秒,線程2獲取lock2 休眠1秒,1秒事後,而後線程1再獲取lock2,線程2再去得到lock1就會發生死鎖。這是咱們又執行了checkDeadLock()方法,來檢查JVM中是否有死鎖,若是有死鎖,則把發生死鎖的線程執行interrupt()方法,使該線程響應中斷,從而避免發生死鎖。(實際應用中,檢查死鎖能夠單獨開啓一個daemon線程,每間隔一段時間檢查一下是否發生死鎖,若是有則預警、記錄日誌、或中斷該線程避免死鎖)

相關文章
相關標籤/搜索