死鎖是兩個或更多線程阻塞着等待其它處于死鎖狀态的線程所持有的鎖。死鎖通常發生在多個線程同時但以不同的順序請求同一組鎖的時候。
例如,如果線程1鎖住了a,然後嘗試對b進行加鎖,同時線程2已經鎖住了b,接着嘗試對a進行加鎖,這時死鎖就發生了。線程1永遠得不到b,線程2也永遠
得不到a,并且它們永遠也不會知道發生了這樣的事情。為了得到彼此的對象(a和b),它們将永遠阻塞下去。這種情況就是一個死鎖。
該情況如下:
這裡有一個treenode類的例子,它調用了不同執行個體的synchronized方法:
如果線程1調用parent.addchild(child)方法的同時有另外一個線程2調用child.setparent(parent)方法,兩個線程中的parent表示的是同一個對象,child亦然,此時就會發生死鎖。下面的僞代碼說明了這個過程:
首先線程1調用parent.addchild(child)。因為addchild()是同步的,是以線程1會對parent對象加鎖以不讓其它線程通路該對象。
然後線程2調用child.setparent(parent)。因為setparent()是同步的,是以線程2會對child對象加鎖以不讓其它線程通路該對象。
現在child和parent對象被兩個不同的線程鎖住了。接下來線程1嘗試調用child.setparentonly()方法,但是由于child對
象現在被線程2鎖住的,是以該調用會被阻塞。線程2也嘗試調用parent.addchildonly(),但是由于parent對象現在被線程1鎖住,
導緻線程2也阻塞在該方法處。現在兩個線程都被阻塞并等待着擷取另外一個線程所持有的鎖。
注意:像上文描述的,這兩個線程需要同時調用parent.addchild(child)和child.setparent(parent)方法,并且是同一個parent對象和同一個child對象,才有可能發生死鎖。上面的代碼可能運作一段時間才會出現死鎖。
這些線程需要同時獲得鎖。舉個例子,如果線程1稍微領先線程2,然後成功地鎖住了a和b兩個對象,那麼線程2就會在嘗試對b加鎖的時候被阻塞,這樣死鎖就不會發生。因為線程排程通常是不可預測的,是以沒有一個辦法可以準确預測什麼時候死鎖會發生,僅僅是可能會發生。
死鎖可能不止包含2個線程,這讓檢測死鎖變得更加困難。下面是4個線程發生死鎖的例子:
線程1等待線程2,線程2等待線程3,線程3等待線程4,線程4等待線程1。
更加複雜的死鎖場景發生在資料庫事務中。一個資料庫事務可能由多條sql更新請求組成。當在一個事務中更新一條記錄,這條記錄就會被鎖住避免其他事務的更新請求,直到第一個事務結束。同一個事務中每一個更新請求都可能會鎖住一些記錄。
當多個事務同時需要對一些相同的記錄做更新操作時,就很有可能發生死鎖,例如:
因為鎖發生在不同的請求中,并且對于一個事務來說不可能提前知道所有它需要的鎖,是以很難檢測和避免資料庫事務中的死鎖。