進(jìn)程:正在執(zhí)行的程序建炫,是一個(gè)動(dòng)態(tài)的過(guò)程
線程:是進(jìn)程中用于控制程序執(zhí)行的控制單元(執(zhí)行路徑,執(zhí)行情景)
進(jìn)程中至少有一個(gè)線程末荐。
在Java VM(java虛擬機(jī))啟動(dòng)的時(shí)候會(huì)有一個(gè)進(jìn)程java.exe.
該進(jìn)程中至少一個(gè)線程負(fù)責(zé)java程序的執(zhí)行墩新。
而且這個(gè)線程運(yùn)行的代碼存在于main方法中狸涌。
該線程稱之為主線程驯用。
擴(kuò)展:其實(shí)更細(xì)節(jié)說(shuō)明jvm挖腰,jvm啟動(dòng)不止一個(gè)線程街夭,還有負(fù)責(zé)垃圾回收機(jī)制的線程砰碴。
創(chuàng)建線程
創(chuàng)建線程的第一種方式:繼承Thread類
步驟:
1,定義類繼承Thread板丽。
2呈枉,復(fù)寫(xiě)Thread類中的run方法。
目的:將自定義代碼存儲(chǔ)在run方法埃碱。讓線程運(yùn)行猖辫。
3,調(diào)用線程的start方法砚殿,
該方法兩個(gè)作用:?jiǎn)?dòng)線程啃憎,調(diào)用run方法。
發(fā)現(xiàn)運(yùn)行結(jié)果每一次都不同似炎。因?yàn)槎鄠€(gè)線程都獲取cpu的執(zhí)行權(quán)辛萍。cpu執(zhí)行到誰(shuí)悯姊,誰(shuí)就運(yùn)行。
明確一點(diǎn)贩毕,在某一個(gè)時(shí)刻悯许,只能有一個(gè)程序在運(yùn)行(多核除外)。cpu在做著快速的切換辉阶,以達(dá)到看上去是同時(shí)運(yùn)行的效果先壕。
我們可以形象把多線程的運(yùn)行形容為在互相搶奪cpu的執(zhí)行權(quán)。
這就是多線程的一個(gè)特性:隨機(jī)性谆甜。誰(shuí)搶到誰(shuí)執(zhí)行启上,至于執(zhí)行多長(zhǎng),cpu說(shuō)的算店印。
為什么要覆蓋run方法呢冈在?
Thread類用于描述線程。該類就定義了一個(gè)功能按摘,用于存儲(chǔ)其他線程(非主線程)要運(yùn)行的代碼包券。該存儲(chǔ)功能就是run方法。
也就是說(shuō)Thread類中的run方法炫贤,用于存儲(chǔ)線程要運(yùn)行的代碼溅固。
創(chuàng)建線程的第二種方式:實(shí)現(xiàn)Runable接口
步驟:
1,定義類實(shí)現(xiàn)Runnable接口
2兰珍,覆蓋Runnable接口中的run方法侍郭。
將線程要運(yùn)行的代碼存放在該run方法中。
3掠河,通過(guò)Thread類建立線程對(duì)象亮元。
4,將Runnable接口的子類對(duì)象作為實(shí)際參數(shù)傳遞給Thread類的構(gòu)造函數(shù)唠摹。
為什么要將Runnable接口的子類對(duì)象傳遞給Thread的構(gòu)造函數(shù)爆捞。
因?yàn)椋远x的run方法所屬的對(duì)象是Runnable接口的子類對(duì)象勾拉。
所以要讓線程去指定指定對(duì)象的run方法煮甥。就必須明確該run方法所屬對(duì)象。
5藕赞,調(diào)用Thread類的start方法開(kāi)啟線程并調(diào)用Runnable接口子類的run方法成肘。
兩種創(chuàng)建現(xiàn)場(chǎng)的區(qū)別
實(shí)現(xiàn)方式和繼承方式有什么區(qū)別呢?
實(shí)現(xiàn)方式好處:避免了單繼承的局限性斧蜕。
在定義線程時(shí)双霍,建立使用實(shí)現(xiàn)方式。
兩種方式區(qū)別:
繼承Thread:線程代碼存放Thread子類run方法中。
實(shí)現(xiàn)Runnable店煞,線程代碼存在接口的子類的run方法蟹演。
線程中的方法
線程都有自己默認(rèn)的名稱:Thread-編號(hào) 該編號(hào)從0開(kāi)始。
線程中的常用方法
static Thread currentThread():獲取當(dāng)前線程對(duì)象顷蟀。
getName(): 獲取線程名稱酒请。
設(shè)置線程名稱:setName或者構(gòu)造函數(shù)。
多線程中出現(xiàn)的問(wèn)題
多線程的運(yùn)行出現(xiàn)了安全問(wèn)題鸣个。
問(wèn)題的原因:
當(dāng)多條語(yǔ)句在操作同一個(gè)線程共享數(shù)據(jù)時(shí)羞反,一個(gè)線程對(duì)多條語(yǔ)句只執(zhí)行了一部分,還沒(méi)有執(zhí)行完囤萤,
另一個(gè)線程參與進(jìn)來(lái)執(zhí)行昼窗。導(dǎo)致共享數(shù)據(jù)的錯(cuò)誤。
解決辦法:
對(duì)多條操作共享數(shù)據(jù)的語(yǔ)句涛舍,只能讓一個(gè)線程都執(zhí)行完澄惊。在執(zhí)行過(guò)程中,其他線程不可以參與執(zhí)行富雅。
Java對(duì)于多線程的安全問(wèn)題提供了專業(yè)的解決方式掸驱。
就是同步代碼塊,方法如下
synchronized(對(duì)象)//任意對(duì)象都行
{
需要被同步的代碼
}
對(duì)象如同鎖没佑。持有鎖的線程可以在同步中執(zhí)行毕贼。
沒(méi)有持有鎖的線程即使獲取cpu的執(zhí)行權(quán),也進(jìn)不去蛤奢,因?yàn)闆](méi)有獲取鎖鬼癣。
同步的前提:
1,必須要有兩個(gè)或者兩個(gè)以上的線程啤贩。
2待秃,必須是多個(gè)線程使用同一個(gè)鎖。
必須保證同步中只能有一個(gè)線程在運(yùn)行瓜晤。
好處:解決了多線程的安全問(wèn)題锥余。
弊端:多個(gè)線程都需要判斷鎖,較為消耗資源痢掠,
示例代碼如下:
public class TicketDemo2 {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable {
private int tick = 100;
Object obj = new Object();// 為synchronized提供對(duì)象
public void run() {
while (true) {
synchronized (obj) {
if (tick > 0) {
try{Thread.sleep(50);}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + "....sale : " + tick--);
}
}
}
}
}
此段代碼的是模擬賣票,假設(shè)有100張票嘲恍,然后有4個(gè)線程同時(shí)賣票足画。
可以在Ticket類中有synchronized同步代碼塊,當(dāng)有同步代碼塊時(shí)佃牛,持有synchronized同步鎖的線程可以在同步中執(zhí)行淹辞。沒(méi)有持有鎖的線程即使獲取cpu的執(zhí)行權(quán),也進(jìn)不去俘侠,因?yàn)闆](méi)有獲取synchronized同步鎖象缀。線程將依序執(zhí)行蔬将,也就是有一個(gè)線程在進(jìn)入時(shí),另一個(gè)線程無(wú)法進(jìn)入央星。
如果沒(méi)有synchronized同步鎖霞怀,那么有可能會(huì)有以下問(wèn)題產(chǎn)生,當(dāng)tick剩下1張票的時(shí)候莉给,t1現(xiàn)場(chǎng)進(jìn)入了毙石,然后遇到Thread.sleep(50),失去執(zhí)行權(quán)颓遏。
接著t2線程執(zhí)行了了徐矩,此時(shí)tick仍然是1張票,因此t2也進(jìn)入if語(yǔ)句叁幢,也遇到sleep()方法滤灯,失去了執(zhí)行權(quán)。
然后t3線程也來(lái)了,遇到了與t1/t2一樣的狀況。
t4線程也既有可能遇到這種情況纵装。
最后t1開(kāi)始執(zhí)行了夺艰,tick為0了。但是此時(shí)t2也在if語(yǔ)句中上荡,也執(zhí)行語(yǔ)句,tick就為-1,然后是t3/t4拂募,導(dǎo)致了代碼運(yùn)行的結(jié)果出現(xiàn)了負(fù)數(shù),與設(shè)想中的不同窟她!
上面所描述的問(wèn)題就是多線程中所要解決的問(wèn)題陈症,這也是synchronized同步鎖的使用情景
同步鎖
synchronized可以給代碼塊上鎖,也可以給方法上鎖震糖,如下:
// 同步代碼塊
synchronized (對(duì)象) {
····· 代碼塊
}
// 同步方法
public synchronized void show (){}
那么問(wèn)題來(lái)了录肯,在同步代碼塊中我們可以很清楚的看到synchronized給哪一個(gè)對(duì)象上鎖,那么同步方法又是給誰(shuí)上鎖呢吊说?
同步函數(shù)用的是哪一個(gè)鎖呢论咏?
函數(shù)需要被對(duì)象調(diào)用。那么函數(shù)都有一個(gè)所屬對(duì)象引用颁井,就是this厅贪,所以同步函數(shù)使用的鎖是this。
通過(guò)該程序進(jìn)行驗(yàn)證雅宾。
使用兩個(gè)線程來(lái)買票养涮。一個(gè)線程在同步代碼塊中。一個(gè)線程在同步函數(shù)中。都在執(zhí)行買票動(dòng)作贯吓。
代碼如下:
public class TicketDemo2 {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
// 讓主線程休眠懈凹,將執(zhí)行權(quán)移交給t1/t2線程
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
}
}
class Ticket implements Runnable {
private int tick = 100;
Object obj = new Object();
// 使用flag
boolean flag = true;
public void run() {
if (flag) {
while (true) {
// 使用的鎖是obj
synchronized (obj) {
if (tick > 0) {
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + "....code : " + tick--);
}
}
}
} else
while (true)
show();
}
// 同步方法的鎖對(duì)象是this,即所屬對(duì)象的引用
public synchronized void show() {
if (tick > 0) {
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + "....show.... : " + tick--);
}
}
}
上述代碼中添加了同步方法悄谐,并且設(shè)置了flag布爾值介评,用以讓線程能在中途從同步代碼塊中切換到同步方法中。
然后再控制臺(tái)中看到結(jié)果:
Thread-1....show.... : 4
Thread-0....code : 3
Thread-1....show.... : 2
Thread-1....show.... : 1
Thread-0....code : 0
這里居然賣出了0張票尊沸,這明顯是一個(gè)錯(cuò)誤的結(jié)果威沫,這是為什么呢?
答案在于我們雖然在多線程中使用了同步鎖洼专,但是我們的鎖對(duì)象并不是同一個(gè)對(duì)象棒掠,因?yàn)樵谕酱a塊中使用的是obj對(duì)象,如下:
synchronized (obj) {
·····代碼省略
}
但是當(dāng)obj替換成this的時(shí)候屁商,我們就能輸出正確的結(jié)果烟很,不在輸出0張票。
這足以證明在同步方法中使用的鎖對(duì)象就是所屬對(duì)象引用蜡镶。
靜態(tài)同步方法
static實(shí)際上也能使用同步鎖雾袱,我們將上述代碼修改,首先將同步方法修改為靜態(tài)同步方法官还,然后將tick也標(biāo)識(shí)為static芹橡,代碼如下:
private static int tick = 100;
···
public static synchronized void show(){
····
}
調(diào)用方法跟前面一樣。
這時(shí)再次輸出了tick=0的錯(cuò)誤結(jié)果望伦!
這又是為什么呢林说?
這是因?yàn)殪o態(tài)方法中的同步鎖對(duì)象使用的不是this,因?yàn)殪o態(tài)方法中也不可以定義this屯伞。
其原因在于靜態(tài)進(jìn)內(nèi)存時(shí)腿箩,內(nèi)存中沒(méi)有本類對(duì)象,但是一定有該類對(duì)應(yīng)的字節(jié)碼文件對(duì)象劣摇。
即 類名.class 該對(duì)象的類型是Class
靜態(tài)的同步方法珠移,使用的鎖是該方法所在類的字節(jié)碼文件對(duì)象。 類名.class末融。
所以我們將同步代碼塊中的所對(duì)象修改為T(mén)icket.class時(shí)就能輸出正確結(jié)果钧惧,此時(shí)Ticket完整代碼如下:
class Ticket implements Runnable {
private static int tick = 100;
Object obj = new Object();
boolean flag = true;
public void run() {
if (flag) {
while (true) {
synchronized (Ticket.class) {
if (tick > 0) {
try{Thread.sleep(50);}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + "....code : " + tick--);
}
}
}
} else
while (true)
show();
}
// 靜態(tài)方法的所對(duì)象是 類名.class
public static synchronized void show() {
if (tick > 0) {
try{Thread.sleep(50);}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + "....show.... : " + tick--);
}
}
}
單例模式中的懶漢式
單例模式中的懶漢式寫(xiě)法其實(shí)頗為復(fù)雜,因?yàn)橐紤]到多線程的問(wèn)題勾习。如果不添加synchronized進(jìn)行同步垢乙,在多線程的情況下,仍有可能導(dǎo)致創(chuàng)建了多個(gè)單例的實(shí)例语卤,這就違背了單例模式的設(shè)計(jì)出初衷,因此必須添加synchronized進(jìn)行同步,正確寫(xiě)法如下:
class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s == null)
s = new Single();
}
}
return s;
}
}
懶漢式與餓漢式的區(qū)別在于懶漢式用于延時(shí)加載粹舵。
而懶漢式出現(xiàn)的問(wèn)題在于使用多線程的時(shí)候創(chuàng)建多個(gè)實(shí)例钮孵,這時(shí)可以使用同步解決。
使用synchronized同步也是有技巧的眼滤,如果使用同步方法也是可以的巴席,代碼如下:
public synchronized static Single getInstance() {
if (s == null) {
if (s == null)
s = new Single();
}
return s;
}
但這種寫(xiě)法會(huì)導(dǎo)致效率稍微低下,因此一般都采用雙重判斷的同步代碼塊的寫(xiě)法诅需。
同時(shí)如果是使用了static靜態(tài)符合漾唉,靜態(tài)代碼塊所使用的同步鎖為該類所屬的字節(jié)碼對(duì)象!
死鎖
產(chǎn)生死鎖的四個(gè)必要條件:
(1) 互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程使用堰塌。
(2) 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí)赵刑,對(duì)已獲得的資源保持不放。
(3) 不剝奪條件:進(jìn)程已獲得的資源场刑,在末使用完之前般此,不能強(qiáng)行剝奪。
(4) 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系牵现。
同步中嵌套同步铐懊,但是它們之間的鎖卻不同,容易導(dǎo)致死鎖瞎疼,下面是死鎖示例:
public DeadLockTest TicketDemo2 {
public static void main(String[] args) {
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
class Test implements Runnable {
private boolean flag;
Test(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
while (true) {
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName() + "...if locka ");
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName() + "..if lockb");
}
}
}
} else {
while (true) {
synchronized (MyLock.lockb) {
System.out.println(Thread.currentThread().getName() + "..else lockb");
synchronized (MyLock.locka) {
System.out.println(Thread.currentThread().getName() + ".....else locka");
}
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
在上面的例子中Test在t1線程中進(jìn)入if語(yǔ)句得到了MyLock.locka的鎖科乎,然后進(jìn)入了第二個(gè)代碼塊中需要MyLock.lockb的鎖才能進(jìn)行下一步。
但是此時(shí)t2線程也開(kāi)始執(zhí)行了贼急,它先進(jìn)入了Test中的else語(yǔ)句茅茂,獲得了MyLock.locka的鎖,需要MyLock.locka的所完成同步代碼竿裂。
此時(shí)陷入了死局玉吁,t1持有了MyLock.locka的鎖,但是無(wú)法獲得MyLock.lockb的鎖執(zhí)行完線程腻异,也無(wú)法釋放MyLock.locka的鎖进副。
而t2線程同樣如此,持有MyLock.lockb的鎖悔常,但是無(wú)法獲得MyLock.locka的鎖執(zhí)行完線程影斑,也無(wú)法釋放MyLock.locka的鎖。
在寫(xiě)程序的時(shí)候應(yīng)該避免死鎖的產(chǎn)生机打。
線程間通信
線程間通訊: 其實(shí)就是多個(gè)線程在操作同一個(gè)資源矫户,但是操作的動(dòng)作不同。
wait:
notify();
notifyAll();
notify()方法用于喚醒等待中的線程残邀,一般線程使用了wait()方法皆辽,會(huì)進(jìn)入線程池中等待執(zhí)行柑蛇,此時(shí)使用notify()方法一般喚醒線程池中的第一個(gè)等待線程。
都使用在同步中驱闷,因?yàn)橐獙?duì)持有監(jiān)視器(鎖)的線程操作耻台。
所以要使用在同步中,因?yàn)橹挥型讲啪哂墟i空另。
為什么這些操作線程的方法要定義Object類中呢盆耽?
因?yàn)檫@些方法在操作同步中線程時(shí),都必須要標(biāo)識(shí)它們所操作線程所持有的鎖扼菠,
只有同一個(gè)鎖上的被等待線程摄杂,可以被同一個(gè)鎖上notify喚醒。
不可以對(duì)不同鎖中的線程進(jìn)行喚醒循榆。
也就是說(shuō)析恢,等待和喚醒必須是同一個(gè)鎖。
而鎖可以是任意對(duì)象冯痢,所以可以被任意對(duì)象調(diào)用的方法定義Object類中氮昧。
多線程生產(chǎn)消費(fèi)者示例
下列代碼為正確示例:
class ProducerConsumerDemo {
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*
* 對(duì)于多個(gè)生產(chǎn)者和消費(fèi)者。 為什么要定義while判斷標(biāo)記浦楣。 原因:讓被喚醒的線程再一次判斷標(biāo)記袖肥。
* 為什么定義notifyAll, 因?yàn)樾枰獑拘褜?duì)方線程振劳。 因?yàn)橹挥胣otify椎组,容易出現(xiàn)只喚醒本方線程的情況。導(dǎo)致程序中的所有線程都等待历恐。
*/
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
// t1 t2
public synchronized void set(String name) {
while (flag)
try {this.wait();} catch (Exception e) {} // t1(放棄資格) t2(獲取資格)
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName() + "...生產(chǎn)者.." + this.name);
flag = true;
this.notifyAll();
}
// t3 t4
public synchronized void out() {
while (!flag)
try { wait();} catch (Exception e) {} // t3(放棄資格) t4(放棄資格)
System.out.println(Thread.currentThread().getName() + "...消費(fèi)者........." + this.name);
flag = false;
this.notifyAll();
}
}
class Producer implements Runnable {
private Resource res;
Producer(Resource res) {
this.res = res;
}
public void run() {
while (true) {
res.set("+商品+");
}
}
}
class Consumer implements Runnable {
private Resource res;
Consumer(Resource res) {
this.res = res;
}
public void run() {
while (true) {
res.out();
}
}
}
在多線程的情況下寸癌,通用的做法是使用while循環(huán) + notifyAll()喚醒方法來(lái)判斷執(zhí)行代碼。
而在單生產(chǎn)者和單消費(fèi)者的情況下使用if循環(huán) + notify()來(lái)判斷執(zhí)行代碼弱贼。
首先分析一下使用if循環(huán) + notify()在多生產(chǎn)者和多消費(fèi)者中產(chǎn)生的問(wèn)題蒸苇,使用這種方法會(huì)導(dǎo)致程序執(zhí)行兩次生產(chǎn),一次消費(fèi)的情況吮旅,又或者是兩次消費(fèi)一次生產(chǎn)的情況溪烤。
關(guān)鍵代碼如下:
public synchronized void set(String name)
{
if(flag)
try{this.wait();}catch(Exception e){}//t1(放棄資格) t2(獲取資格)
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生產(chǎn)者.."+this.name);
flag = true;
this.notify();
}
// t3 t4
public synchronized void out()
{
if(!flag)
try{wait();}catch(Exception e){}//t3(放棄資格) t4(放棄資格)
System.out.println(Thread.currentThread().getName()+"...消費(fèi)者........."+this.name);
flag = false;
this.notify();
}
產(chǎn)生這種情況的原因在于如果生產(chǎn)者(t1/t2)先獲得了執(zhí)行權(quán),此時(shí)flag為false庇勃,因此t1直接往下執(zhí)行檬嘀,生產(chǎn)出一個(gè)產(chǎn)品(執(zhí)行println),然后將flag設(shè)為true责嚷,但是此時(shí)t1仍然能繼續(xù)執(zhí)行(本案例中線程沒(méi)有退出機(jī)制鸳兽,因此一旦執(zhí)行將持續(xù)執(zhí)行不停止),于是t1再次在if語(yǔ)句中判斷罕拂,然而flag已經(jīng)為true揍异,所以t1執(zhí)行了wait()方法全陨,放棄執(zhí)行權(quán)。
重點(diǎn)來(lái)了蒿秦,在這個(gè)時(shí)候t2生產(chǎn)者烤镐,t3/t4消費(fèi)者都有執(zhí)行權(quán),假設(shè)這時(shí)t2取得執(zhí)行權(quán)棍鳖,執(zhí)行下去了,此時(shí)flag仍為true碗旅,因此t2也進(jìn)如wait()階段渡处。
然后就輪到t3/t4執(zhí)行了,他們消費(fèi)一次之后祟辟,將flag設(shè)為false医瘫,然后喚醒了在線程池中的線程,而notify()會(huì)喚醒處于線程池中的第一個(gè)等待的線程旧困,也就是t1醇份。然而此時(shí)t3仍舊執(zhí)行,但是遇到flag為false吼具,于是t3執(zhí)行了wait()方法僚纷。
這時(shí)線程中擁有執(zhí)行權(quán)的就剩下生產(chǎn)者t1和消費(fèi)者t4了,然而計(jì)算機(jī)執(zhí)行了t4線程拗盒,因?yàn)閒lag為false的緣故怖竭,t4也等待了。
此時(shí)在線程池中等待的順序依次為 t2陡蝇,t3痊臭,t4。
t1開(kāi)始執(zhí)行了登夫,然后把flag設(shè)置為true广匙,并且執(zhí)行了notify()方法,喚醒了t2線程恼策。
但是之前t2已經(jīng)通過(guò)了if的判斷鸦致,處于wait()狀態(tài),因此被喚醒的時(shí)候不在執(zhí)行if判斷戏蔑,直接往下執(zhí)行蹋凝,因此在t1之后也執(zhí)行了生產(chǎn)命令。
最后喚醒了t3消費(fèi)者总棵,然后繼續(xù)執(zhí)行鳍寂。
這就是聯(lián)系兩次執(zhí)行了生產(chǎn)命令的原因。
上面說(shuō)的有點(diǎn)啰嗦情龄,但這非常重要迄汛,需要細(xì)細(xì)體會(huì)捍壤。
了解了為什么在多生產(chǎn)/消費(fèi)者執(zhí)行的情況下,使用if判斷和notify()方法喚醒會(huì)持續(xù)執(zhí)行兩次生產(chǎn)或者消費(fèi)的命令后鞍爱,這是因?yàn)閕f只判斷一次flag的情況鹃觉,當(dāng)線程被喚醒之后會(huì)直接往下執(zhí)行的緣故。
那么我們?nèi)绻褂脀hile加上notify()方法睹逃,讓線程每次被喚醒的時(shí)候都進(jìn)行判斷呢盗扇?
這時(shí)產(chǎn)生了死鎖。
這個(gè)結(jié)束比較簡(jiǎn)單沉填,首先t1運(yùn)行疗隶,flag為true,然后t1繼續(xù)執(zhí)行的時(shí)候就進(jìn)入wait()方法翼闹,如果此時(shí)t2執(zhí)行的話斑鼻,也進(jìn)入wait()方法中。
然后t3/t4執(zhí)行了猎荠,t3將flag設(shè)為false坚弱,并喚醒了t1,然后t3繼續(xù)執(zhí)行便執(zhí)行了wait()方法关摇,如果此時(shí)t4執(zhí)行了荒叶,也會(huì)進(jìn)入wait()方法中,這時(shí)消費(fèi)者線程全滅
最后剩下t1在執(zhí)行了拒垃,t1將flag設(shè)置true之后停撞,喚醒了t2,但是此時(shí)flag為true悼瓮,而每次喚醒都會(huì)在while進(jìn)行一次循環(huán)戈毒,此時(shí)悲催的t2再次進(jìn)入wait()方法,生產(chǎn)者線程也全滅了横堡!
因?yàn)橹挥胣otify埋市,容易出現(xiàn)只喚醒本方線程的情況。導(dǎo)致程序中的所有線程都等待命贴。
這就是產(chǎn)生死鎖的原因道宅。
所以在多生產(chǎn)多消費(fèi)者的情況下使用while讓每個(gè)線程在喚醒的時(shí)候再次進(jìn)行判斷,然后使用notifyAll()喚醒所有的線程胸蛛,讓每個(gè)線程再次做判斷是否繼續(xù)往下執(zhí)行污茵,這樣才能確保每次都有線程能夠執(zhí)行,也能確保每個(gè)線程都能被喚醒葬项,不會(huì)導(dǎo)致死鎖泞当。
使用jdk1.5之后的Lock接口進(jìn)行同步
上面生產(chǎn)者和消費(fèi)者的使用方法太過(guò)繁瑣了,JDK1.5 中提供了多線程升級(jí)解決方案民珍。
將同步Synchronized替換成現(xiàn)實(shí)Lock操作襟士。
將Object中的wait盗飒,notify notifyAll,替換了Condition對(duì)象陋桂。該對(duì)象可以Lock鎖 進(jìn)行獲取逆趣。
在下列示例中,實(shí)現(xiàn)了本方只喚醒對(duì)方操作嗜历。
Lock:替代了Synchronized
lock
unlock
newCondition()
Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();
我們修改上面生產(chǎn)者和消費(fèi)者案例中的Resource的代碼宣渗,將Synchronized替換為L(zhǎng)ock,將notifyAll替換為Condition秸脱,代碼如下:
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
// t1 t2
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name) throws InterruptedException {
lock.lock();
try {
while (flag)
condition_pro.await();// t1,t2等待
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName() + "...生產(chǎn)者.." + this.name);
flag = true;
condition_con.signal(); // 喚醒t3落包,t4中的一個(gè)
} finally {
lock.unlock();// 釋放鎖的動(dòng)作一定要執(zhí)行。
}
}
// t3 t4
public void out() throws InterruptedException {
lock.lock();
try {
while (!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName() + "...消費(fèi)者........." + this.name);
flag = false;
condition_pro.signal();
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
private Resource res;
Producer(Resource res) {
this.res = res;
}
public void run() {
while (true) {
try {
res.set("+商品+");
} catch (InterruptedException e) {
}
}
}
}
class Consumer implements Runnable {
private Resource res;
Consumer(Resource res) {
this.res = res;
}
public void run() {
while (true) {
try {
res.out();
} catch (InterruptedException e) {
}
}
}
}
使用Condition的好處在于一個(gè)Lock鎖可以擁有多個(gè)Condition對(duì)象摊唇。
而在上面的代碼中定義了兩個(gè)Condition對(duì)象,一個(gè)為生產(chǎn)者condition_pro的條件涯鲁,另一個(gè)為消費(fèi)者condition_con條件巷查。
這使condition_pro可以使用自己的await()方法和condition_pro.signal()喚醒方法,這樣就能讓在生產(chǎn)者中喚醒消費(fèi)者抹腿,而在消費(fèi)者中喚醒生產(chǎn)者岛请,不會(huì)發(fā)生像Synchornized中的那種喚醒了本方線程的失誤事件。
但是要注意警绩,使用Lock的時(shí)候崇败,一定要在finally中釋放鎖,即調(diào)用lock.unlock()方法肩祥。
停止線程的方式
stop方法已經(jīng)過(guò)時(shí)后室。
如何停止線程?
只有一種混狠,run方法結(jié)束岸霹。
開(kāi)啟多線程運(yùn)行,運(yùn)行代碼通常是循環(huán)結(jié)構(gòu)将饺。
只要控制住循環(huán)贡避,就可以讓run方法結(jié)束,也就是線程結(jié)束予弧。
示例代碼中給Runnable對(duì)象添加了flag標(biāo)記刮吧,控制了while循環(huán),代碼如下:
class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while (true) {
if (num++ == 60) {
st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName() + "......." + num);
}
System.out.println("over");
}
}
class StopThread implements Runnable {
private boolean flag = true;
public void run() {
while (flag) {
System.out.println(Thread.currentThread().getName() + "....run");
}
}
public void changeFlag() {
flag = false;
}
}
特殊情況:
當(dāng)線程處于了凍結(jié)狀態(tài)掖蛤。
就不會(huì)讀取到標(biāo)記杀捻。那么線程就不會(huì)結(jié)束。
當(dāng)沒(méi)有指定的方式讓凍結(jié)的線程恢復(fù)到運(yùn)行狀態(tài)是坠七,這時(shí)需要對(duì)凍結(jié)進(jìn)行清除水醋。
強(qiáng)制讓線程恢復(fù)到運(yùn)行狀態(tài)中來(lái)旗笔,這樣就可以操作標(biāo)記讓線程結(jié)束。Thread類提供該方法 interrupt();
示例如下:
class StopThread implements Runnable {
private boolean flag = true;
public synchronized void run() {
while (flag) {
try {
wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "....Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName() + "....run");
}
}
public void changeFlag() {
flag = false;
}
}
class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while (true) {
if (num++ == 60) {
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName() + "......." + num);
}
System.out.println("over");
}
}
要注意的是使用 interrupt();方法并不是正確的停止線程的方式拄踪,而是以拋出異常的方式停止線程蝇恶,要慎用。
Thread中的其他方法
setDaemon(boolean on) 將該線程標(biāo)記為守護(hù)線程或用戶線程惶桐。簡(jiǎn)單的說(shuō)就是將線程設(shè)置為后臺(tái)線程撮弧,此時(shí)將會(huì)與主線程搶奪CPU資源,而且主線程結(jié)束時(shí)姚糊,后臺(tái)線程會(huì)自動(dòng)結(jié)束贿衍。
修改上面的StopThreadDemo代碼,將t1/t2線程設(shè)置為后臺(tái)線程救恨,如下
class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while (true) {
if (num++ == 60) {
break;
}
System.out.println(Thread.currentThread().getName() + "......." + num);
}
System.out.println("over");
}
}
class StopThread implements Runnable {
private boolean flag = true;
public void run() {
while (flag) {
System.out.println(Thread.currentThread().getName() + "....run");
}
}
public void changeFlag() {
flag = false;
}
}
此時(shí)并沒(méi)有使用任何的方式結(jié)束線程贸辈,卻發(fā)現(xiàn)當(dāng)主線程結(jié)束時(shí),t1/t2線程也結(jié)束了肠槽。
join()方法與yield()方法
join:
當(dāng)A線程執(zhí)行到了B線程的.join()方法時(shí)擎淤,A就會(huì)等待。等B線程都執(zhí)行完秸仙,A才會(huì)執(zhí)行嘴拢。
join可以用來(lái)臨時(shí)加入線程執(zhí)行,代碼示例:
class JoinDemo {
public static void main(String[] args) {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
for (int x = 0; x < 80; x++) {
System.out.println("main....."+x);
}
System.out.println("over");
}
}
class Demo implements Runnable {
public void run() {
for (int x = 0; x < 70; x++) {
System.out.println(Thread.currentThread().toString() + "....." + x);
}
}
}
toString():返回該線程的字符串你表示形式寂纪,包括線程名稱席吴、優(yōu)先級(jí)和線程組
yield():暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程捞蛋,代碼示例:
class YieldDemo {
public static void main(String[] args) {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
System.out.println("over");
}
}
class Demo implements Runnable {
public void run() {
for (int x = 0; x < 70; x++) {
System.out.println(Thread.currentThread().toString() + "....." + x);
Thread.yield();
}
}
}
線程間通訊
Java線程間通訊其實(shí)也就是使用Synchronized和Object類方法wait(),notify(),notifyAll()的共同使用孝冒,保證運(yùn)行結(jié)果正確,這些都在上述代碼有所涉及襟交。