死鎖應該能夠說是併發編程中比較常見的一種狀況,能夠說若是程序產生了死鎖那將會對程序帶來致命的影響;因此排查定位、修復死鎖相當重要;
咱們都知道死鎖是因爲多個對象或多個線程之間相互須要對方鎖持有的鎖而又沒有釋放對方所持有的鎖,致使雙方都永久處於阻塞狀態;編程
如上圖所示,線程1持有對象1的鎖、線程2持有對象2的鎖,持此線程1又想去獲取對象2對象鎖、線程2想獲取對象1對象鎖,此時因爲雙方都沒有獲取到想要的鎖,任務沒完成因此也沒釋放鎖,致使一直僵持呢,因而阻塞、產生死鎖;併發
須要檢測死鎖確定要先有死鎖出現,下面的demo模擬了一個死鎖的產生;ide
public class DeadlockDemo extends Thread { private BaseObj first; private BaseObj second; public DeadlockDemo(String name, BaseObj first, BaseObj second) { super(name); this.first = first; this.second = second; } public void reentrantLock() throws InterruptedException { first.lock(); System.out.println(String.format("%s 持有:%s 對象鎖,等待獲取:%s對象鎖", this.getName(), first, second)); second.lock(); first.unlock(); second.unlock(); } @Override public void run() { try { reentrantLock(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ObjOne one = new ObjOne(); ObjTwo two = new ObjTwo(); DeadlockDemo thread1 = new DeadlockDemo("Thread1", one, two); DeadlockDemo thread2 = new DeadlockDemo("Thread2", two, one); thread1.start(); thread2.start(); thread1.join(); thread2.join(); } }
運行上面的demo將看到程序被阻塞了,無法結束運行;只看到以下運行結果:
Thread1 持有:objOne 對象鎖,等待獲取:objTwo對象鎖 Thread2 持有:objTwo 對象鎖,等待獲取:objOne對象鎖工具
這demo無法結束運行就是因爲產生了死鎖,兩個線程都在相互對待獲取對方所持有的對象鎖;
這時候要解決問題就須要找出哪裏出現了死鎖,經過代碼走查一般不容易發現死鎖,固然咱們這程序很容易發現,由於咱們刻意產生的死鎖;因此就須要工具來檢測死鎖,這裏可用的工具主要有:jconsole、jvisualvm、jstack等,這些工具其實都是jdk自帶的,用法都很相似;性能
這裏使用jvisualvm來檢測當前的demo程序是否產生了死鎖;打開jvisualvm鏈接到當前的應用程序便可看到程序的監控信息,如內存、CPU、性能、GC等等;打開進入線程的tab項查看程序的線程信息,這裏很明顯的就看到了提示該程序被檢測除了死鎖!this
點擊 線程Dump能夠看到線程的堆棧信息,從中能夠看到線程的詳細信息,並定位死鎖;
從上圖能夠看到線程產生死鎖的緣由,Thrad2是等待Thread一、Thread1是等待Thread1, 從下圖的堆棧信息便可定位死鎖產生的位置;
線程
除了發現程序出現問題後咱們去掃描死鎖外,咱們還能夠實時的去掃描程序用於發現程序中是否存在死鎖;
JDK提供了MXBean Api可用於掃描程序是否存在死鎖,ThreadMXBean提供了findDeadlockedThreads()方法,能夠用於找到產生死鎖的線程;這裏在上面的demo程序中添加一個方法用於掃描死鎖,雖然這種方法能夠掃描到死鎖可是因爲每次都對線程打快照對程序性能會有比較大的影響,因此慎用;設計
public static void scanDeadLock() { ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); Runnable runnable = () -> { long[] ids = mxBean.findDeadlockedThreads(); System.out.println("掃描死鎖..."); if (ids != null) { ThreadInfo[] threadInfos = mxBean.getThreadInfo(ids); for (ThreadInfo threadInfo : threadInfos) { System.out.println(threadInfo); } } }; ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(5, Executors.defaultThreadFactory()); executorService.scheduleAtFixedRate(runnable, 1, 5, TimeUnit.SECONDS); }
解決死鎖最好的方法就是避免死鎖了,好比上面的demo咱們能夠把直接使用無參數的lock()方法換爲使用tryLock方法,tryLock還能夠指定獲取鎖超時時間,到了超時時間還沒得到到鎖就會放棄獲取鎖,固然還有其它方法能夠避免死鎖;
一、避免使用多個鎖、長時間持有鎖;
二、設計好多個鎖的獲取順序
三、使用帶超時的獲取鎖方法code
文章首發地址:Solinx
http://www.solinx.co/archives/1196orm