死鎖
成都創(chuàng)新互聯(lián)公司是一家網(wǎng)站設(shè)計公司,集創(chuàng)意、互聯(lián)網(wǎng)應(yīng)用、軟件技術(shù)為一體的創(chuàng)意網(wǎng)站建設(shè)服務(wù)商,主營產(chǎn)品:成都響應(yīng)式網(wǎng)站建設(shè)、成都品牌網(wǎng)站建設(shè)、網(wǎng)絡(luò)營銷推廣。我們專注企業(yè)品牌在網(wǎng)站中的整體樹立,網(wǎng)絡(luò)互動的體驗,以及在手機等移動端的優(yōu)質(zhì)呈現(xiàn)。成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計、移動互聯(lián)產(chǎn)品、網(wǎng)絡(luò)運營、VI設(shè)計、云產(chǎn)品.運維為核心業(yè)務(wù)。為用戶提供一站式解決方案,我們深知市場的競爭激烈,認真對待每位客戶,為客戶提供賞析悅目的作品,網(wǎng)站的價值服務(wù)。
死鎖是這樣一種情形:多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由于線程被無限期地阻塞,因此程序不可能正常終止。
導(dǎo)致死鎖的根源在于不適當?shù)剡\用“synchronized”關(guān)鍵詞來管理線程對特定對象的訪問?!皊ynchronized”關(guān)鍵詞的作用是,確保在某個時刻只有一個線程被允許執(zhí)行特定的代碼塊,因此,被允許執(zhí)行的線程首先必須擁有對變量或?qū)ο蟮呐潘缘脑L問權(quán)。當線程訪問對象時,線程會給對象加鎖,而這個鎖導(dǎo)致其它也想訪問同一對象的線程被阻塞,直至第一個線程釋放它加在對象上的鎖。
由于這個原因,在使用“synchronized”關(guān)鍵詞時,很容易出現(xiàn)兩個線程互相等待對方做出某個動作的情形。代碼一是一個導(dǎo)致死鎖的簡單例子。
//代碼一
class Deadlocker {
int field_1;
private Object lock_1 = new int[1];
int field_2;
private Object lock_2 = new int[1];
public void method1(int value) {
“synchronized” (lock_1) {
“synchronized” (lock_2) {
field_1 = 0; field_2 = 0;
}
}
}
public void method2(int value) {
“synchronized” (lock_2) {
“synchronized” (lock_1) {
field_1 = 0; field_2 = 0;
}
}
}
}
參考代碼一,考慮下面的過程:
◆ 一個線程(ThreadA)調(diào)用method1()。
◆ ThreadA在lock_1上同步,但允許被搶先執(zhí)行。
◆ 另一個線程(ThreadB)開始執(zhí)行。
◆ ThreadB調(diào)用method2()。
◆ ThreadB獲得lock_2,繼續(xù)執(zhí)行,企圖獲得lock_1。但ThreadB不能獲得lock_1,因為ThreadA占有l(wèi)ock_1。
◆ 現(xiàn)在,ThreadB阻塞,因為它在等待ThreadA釋放lock_1。
◆ 現(xiàn)在輪到ThreadA繼續(xù)執(zhí)行。ThreadA試圖獲得lock_2,但不能成功,因為lock_2已經(jīng)被ThreadB占有了。
◆ ThreadA和ThreadB都被阻塞,程序死鎖。
當然,大多數(shù)的死鎖不會這么顯而易見,需要仔細分析代碼才能看出,對于規(guī)模較大的多線程程序來說尤其如此。好的線程分析工具,例如JProbe Threadalyzer能夠分析死鎖并指出產(chǎn)生問題的代碼位置。
隱性死鎖
隱性死鎖由于不規(guī)范的編程方式引起,但不一定每次測試運行時都會出現(xiàn)程序死鎖的情形。由于這個原因,一些隱性死鎖可能要到應(yīng)用正式發(fā)布之后才會被發(fā)現(xiàn),因此它的危害性比普通死鎖更大。下面介紹兩種導(dǎo)致隱性死鎖的情況:加鎖次序和占有并等待。
加鎖次序
當多個并發(fā)的線程分別試圖同時占有兩個鎖時,會出現(xiàn)加鎖次序沖突的情形。如果一個線程占有了另一個線程必需的鎖,就有可能出現(xiàn)死鎖??紤]下面的情形,ThreadA和ThreadB兩個線程分別需要同時擁有l(wèi)ock_1、lock_2兩個鎖,加鎖過程可能如下:
◆ ThreadA獲得lock_1;
◆ ThreadA被搶占,VM調(diào)度程序轉(zhuǎn)到ThreadB;
◆ ThreadB獲得lock_2;
◆ ThreadB被搶占,VM調(diào)度程序轉(zhuǎn)到ThreadA;
◆ ThreadA試圖獲得lock_2,但lock_2被ThreadB占有,所以ThreadA阻塞;
◆ 調(diào)度程序轉(zhuǎn)到ThreadB;
◆ ThreadB試圖獲得lock_1,但lock_1被ThreadA占有,所以ThreadB阻塞;
◆ ThreadA和ThreadB死鎖。
必須指出的是,在代碼絲毫不做變動的情況下,有些時候上述死鎖過程不會出現(xiàn),VM調(diào)度程序可能讓其中一個線程同時獲得lock_1和lock_2兩個鎖,即線程獲取兩個鎖的過程沒有被中斷。在這種情形下,常規(guī)的死鎖檢測很難確定錯誤所在。
占有并等待
如果一個線程獲得了一個鎖之后還要等待來自另一個線程的通知,可能出現(xiàn)另一種隱性死鎖,考慮代碼二。
//代碼二
public class queue {
static java.lang.Object queueLock_;
Producer producer_;
Consumer consumer_;
public class Producer {
void produce() {
while (!done) {
“synchronized” (queueLock_) {
produceItemAndAddItToQueue();
“synchronized” (consumer_) {
consumer_.notify();
}
}
}
}
public class Consumer {
consume() {
while (!done) {
“synchronized” (queueLock_) {
“synchronized” (consumer_) {
consumer_.wait();
}
removeItemFromQueueAndProcessIt();
}
}
}
}
}
}
在代碼二中,Producer向隊列加入一項新的內(nèi)容后通知Consumer,以便它處理新的內(nèi)容。問題在于,Consumer可能保持加在隊列上的鎖,阻止Producer訪問隊列,甚至在Consumer等待Producer的通知時也會繼續(xù)保持鎖。這樣,由于Producer不能向隊列添加新的內(nèi)容,而Consumer卻在等待Producer加入新內(nèi)容的通知,結(jié)果就導(dǎo)致了死鎖。
在等待時占有的鎖是一種隱性的死鎖,這是因為事情可能按照比較理想的情況發(fā)展—Producer線程不需要被Consumer占據(jù)的鎖。盡管如此,除非有絕對可靠的理由肯定Producer線程永遠不需要該鎖,否則這種編程方式仍是不安全的。有時“占有并等待”還可能引發(fā)一連串的線程等待,例如,線程A占有線程B需要的鎖并等待,而線程B又占有線程C需要的鎖并等待等。
要改正代碼二的錯誤,只需修改Consumer類,把wait()移出“synchronized”()即可。
以下為一具體的Java實例說明死鎖:
public
class
DeadLock
implements
Runnable
{
public
int
flag
=
1;
static
Object
o1
=
new
Object();
static
Object
o2
=
new
Object();
public
void
run()
{
System.out.println("flag="
+
flag);
//
當flag==1鎖住o1
if
(flag
==
1)
{
synchronized
(o1)
{
try
{
Thread.sleep(500);
}
catch
(Exception
e)
{
e.printStackTrace();
}
//
只要鎖住o2就完成
synchronized
(o2)
{
System.out.println("1");
}
}
}
//
如果flag==0鎖住o2
if
(flag
==
0)
{
synchronized
(o2)
{
try
{
Thread.sleep(500);
}
catch
(Exception
e)
{
e.printStackTrace();
}
//
只要鎖住o1就完成
synchronized
(o1)
{
System.out.println("0");
}
}
}
}
public
static
void
main(String[]
args)
{
//
實例2個線程類
DeadLock
td1
=
new
DeadLock();
DeadLock
td2
=
new
DeadLock();
td1.flag
=
1;
td2.flag
=
0;
//
開啟2個線程
Thread
t1
=
new
Thread(td1);
Thread
t2
=
new
Thread(td2);
t1.start();
t2.start();
}
}
大概寫了一個例子,給你看看,你的那個例子來搞死鎖比較難搞,主要你是只有一個鎖,沒有所謂的請求不釋放的問題,一般死鎖都需要有兩個鎖或以上的。
public class TestT {
public static void main(String[] args) {
for (int i = 0; i 10; i ++) {
NumThread nt = new NumThread(1,2);
NumThread nt2 = new NumThread(2,1);
nt.start();
nt2.start();
}
}
}
class NumThread extends Thread{
private int a;
private int b;
public NumThread(int a,int b) {
this.a = a;
this.b = b;
}
public void run() {
synchronized(Integer.valueOf(a)) {
System.out.println("xxx" + Thread.currentThread().getName());
synchronized(Integer.valueOf(b)) {
System.out.println("yyy" + Thread.currentThread().getName());
}
}
}
}
這個例子首先需要先了解Integer類的機制,再進行Integer類實例化或轉(zhuǎn)換時,它會緩存-128-127之間的所有對象,因此在這里我們,調(diào)用的1,2至始至終都只有兩個對象。
下面就是死鎖的分析:
當我們執(zhí)行NumThread(1,2)時,鎖的取得沒問題(Integer.valueOf(a)的鎖肯定沒問題),接下來用NumThread(2,1),如果此時NumThread(1,2)已經(jīng)取得了兩個鎖,這里沒問題,執(zhí)行完后可以繼續(xù)取得鎖,但如果NumThread(1,2)只取得a的鎖,而此時NumThread(2,1)取得了b的鎖,這時問題就來了。NumThread(1,2)會等待NumThread(2,1)釋放b鎖,而NumThread(2,1)會等等NumThread(1,2)釋放a鎖。
我用了一個循環(huán)啟動線程是因為發(fā)生的機率不大。
可以引伸到你那個例子,用兩個相同的對象作為鎖。
public class TestT {
public static void main(String[] args) {
S s = new S();
S s2 = new S();
for (int i = 0; i 10; i ++) {
TestSleep ts = new TestSleep(s,s2);
TestSleep ts2 = new TestSleep(s2,s);
ts.start();
ts2.start();
}
}
}
class S{public int i=0;}
class TestSleep extends Thread {
/**
* @param args
*/
private S s=null;
private S s2 = null;
public TestSleep(S s,S s2){
this.s=s;
this.s2=s2;
}
public void run(){
System.out.println("Now is begin Thread-A");
synchronized(s){
System.out.println("Now is begin "+Thread.currentThread().getName());
synchronized(s2) {
System.out.println(s.i);
}
}
}
}