什麼是死鎖
寫線程同步的時候,我在最後舉了個例子:同步代碼塊相當於是廁所(惡臭的好例子),一個人進去,另外一個人只有等他出來才能進去。
假如有這樣一種情況,手紙是另一個同步代碼塊,甲拿了紙,想進廁所,乙在廁所,沒帶紙想用紙。哦吼,這就是個死鎖了。
解決死鎖的基本思路
還是上面的那個例子,讓佔有手紙資源的線程先釋放手紙資源,然後佔據廁所資源的線程就可以拿到手紙,等有廁所資源,手紙資源的線程執行完畢後會釋放這兩個資源,然後另一個資源就可以去上了。。。
/*死鎖模擬-哲學家喫飯的問題
解決方法 :將鎖的粒度加粗,只用一個對象鎖
很難涉及到,加了鎖就要小心死鎖的問題*/
public class TestDeadLock implements Runnable
{
public int flag = 1;
static Object o1 = new Object(), o2 = new Object();
public void run()
{
System.out.println("flag=" + flag);
if (flag == 1)
{
synchronized (o1)
{
try
{
Thread.sleep(500);
}
catch (Exception e)
{
e.printStackTrace();
}
try
{
o1.wait(); //o1對象鎖釋放自己的鑰匙。
}
catch (InterruptedException e)
{
e.printStackTrace();
}
synchronized (o2)
{
System.out.println("1");
}
}
}
if (flag == 0)
{
synchronized (o2)
{
try
{
Thread.sleep(500);
}
catch (Exception e)
{
e.printStackTrace();
}
synchronized (o1)
{
System.out.println("0");
o1.notify(); //o1對象鎖在釋放之前喚醒等待池中的線程繼續執行。所以喚醒動作必須放到裏面。
}
}
}
}
public static void main(String[] args)
{
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 1;
td2.flag = 0;
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
}
其中有一些點要說明
wait()和notify()的調用
- 當前線程在調用wait()/notify()時,必須擁有該對象的同步鎖
- 要在擁有該同步鎖的同步塊中調用
線程通信(生產者消費者問題)
現在有一筐窩窩頭(棧),生產者往框裏放窩窩頭,消費者從框裏取窩窩頭,如果框裏沒有窩窩頭了,消費者就不能取窩窩頭,假如這個框能裝6個窩窩頭,那麼裝滿了後生產者也就停止生產窩窩頭,每個消費者喫兩個窩窩頭,如何解決消費者和生產者之間的通信問題呢?很簡單,加一個旗標標值有沒有窩窩頭就好了。
package thread;
/**
* @author Mike
* @use 生產者消費者問題
*/
public class ProducerConsumer extends Thread {
static SyncStack ss; // 框對象創建
static Thread pro1;
static Thread cus1;
static Thread cus2;
static Thread cus3;
static Thread cus4;
static Thread cus5;
static Thread cus6;
public static void main(String[] args)
{
ss = new SyncStack(); // 框對象創建
Producer p1 = new Producer(ss);
Consumer c1 = new Consumer(ss);
Consumer c2 = new Consumer(ss);
Consumer c3 = new Consumer(ss);
Consumer c4 = new Consumer(ss);
Consumer c5 = new Consumer(ss);
Consumer c6 = new Consumer(ss);
pro1 = new Thread(p1, "廚師1");
cus1 = new Thread(c1, "客戶1");
cus2 = new Thread(c2, "客戶2");
cus3 = new Thread(c3, "客戶3");
cus4 = new Thread(c4, "客戶4");
cus5 = new Thread(c5, "客戶5");
cus6 = new Thread(c6, "客戶6");
pro1.start();
cus1.start();
cus2.start();
cus3.start();
cus4.start();
cus5.start();
cus6.start();
new ProducerConsumer().start();
// 啓動廚師和客戶線程。
/*
* p[0].start(); c[0].start(); c[1].start();
*/
}
public void run()
{
while (true)
{
if ((!cus1.isAlive()) && (!cus2.isAlive()) && (!cus3.isAlive())
&& (!cus4.isAlive()) && (!cus5.isAlive())
&& (!cus6.isAlive()))
{
ss.flag = false;
System.out.println("下班了!");
break;
}
try
{
Thread.sleep(200);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
/**
* 類說明:饅頭類
*/
class WoTou
{
int id;
WoTou(int id)
{
this.id = id;
}
public String toString()
{
return "WoTou : " + id;
}
}
/**
* 類說明:框類
*/
class SyncStack
{
int index = 0;
WoTou[] arrWT = new WoTou[6]; // 框,只能放6個饅頭
boolean flag = true;
/**
* 方法說明:往框裏面放
*
* @param wt
*/
public void push(WoTou wt)
{
while (index >= arrWT.length)
{
try
{
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
arrWT[index] = wt;
index++;
System.out.println(Thread.currentThread().getName() + "生產了:" + wt);
this.notifyAll();
}
/**
* 方法說明:從框裏面取
*
* @return
*/
public WoTou pop()
{
while (index <= 0)
{
try
{
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
index--;
WoTou woTou = arrWT[index];
System.out.println(Thread.currentThread().getName() + "消費了: " + woTou);
this.notifyAll();
return woTou;
}
}
/**
* 類說明:生產者生產饅頭
*/
class Producer implements Runnable
{
SyncStack ss = null;
static int num = 0;
Producer(SyncStack ss)
{
this.ss = ss;
}
public void run()
{
while (ss.flag)
{
synchronized (ss)
{
WoTou wt = new WoTou(num++);
ss.push(wt);
try
{
Thread.sleep((int) (Math.random() * 2000));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
/**
* 類說明:消費者
*/
class Consumer implements Runnable
{
SyncStack ss = null;
int num = 0;
Consumer(SyncStack ss)
{
this.ss = ss;
}
public void run()
{
while (num < 2)
{
synchronized (ss)
{
ss.pop();
num = num + 1;
try
{
Thread.sleep((int) (Math.random() * 2000));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
線程的學習就先到這裏,其實應該還有線程池等其他東西,但現階段沒有學到先放一放,畢竟光線程就是很厚一本書了,教材上才用了一章來講,等基礎知識在紮實點在去看線程的書,然後再總結博客吧。