線程狀態(tài)圖
線程共包括以下5種狀態(tài)。
1. 新建狀態(tài)(New) : 線程對(duì)象被創(chuàng)建后,就進(jìn)入了新建狀態(tài)。例如,Thread thread = new Thread()。
2. 就緒狀態(tài)(Runnable): 也被稱為“可執(zhí)行狀態(tài)”。線程對(duì)象被創(chuàng)建后,其它線程調(diào)用了該對(duì)象的start()方法,從而來啟動(dòng)該線程。例如,thread.start()。處于就緒狀態(tài)的線程,隨時(shí)可能被CPU調(diào)度執(zhí)行。
3. 運(yùn)行狀態(tài)(Running) : 線程獲取CPU權(quán)限進(jìn)行執(zhí)行。需要注意的是,線程只能從就緒狀態(tài)進(jìn)入到運(yùn)行狀態(tài)。
4. 阻塞狀態(tài)(Blocked) : 阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時(shí)停止運(yùn)行。直到線程進(jìn)入就緒狀態(tài),才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:
(01) 等待阻塞 -- 通過調(diào)用線程的wait()方法,讓線程等待某工作的完成。
(02) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因?yàn)殒i被其它線程所占用),它會(huì)進(jìn)入同步阻塞狀態(tài)。
(03) 其他阻塞 -- 通過調(diào)用線程的sleep()或join()或發(fā)出了I/O請(qǐng)求時(shí),線程會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入就緒狀態(tài)。
5. 死亡狀態(tài)(Dead) : 線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
實(shí)現(xiàn)多線程的方式Thread和Runnable
Thread:繼承thread類,實(shí)現(xiàn)run方法,在main函數(shù)中調(diào)用start方法啟動(dòng)線程
Runnable:接口,實(shí)現(xiàn)Runnable接口,作為參數(shù)傳遞給Thread的構(gòu)造函數(shù),在main中調(diào)用start方法
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
class task implements Runnable { private int ticket = 10 ; @Override public void run() { for ( int i = 0 ; i < 20 ; i++) { if ( this .ticket > 0 ) { System.out.println(Thread.currentThread().getName() + " sold ticket " + this .ticket--); } } } }; public class RunnableTest { public static void main(String[]args){ task mytask = new task(); Thread t1 = new Thread(mytask); Thread t2 = new Thread(mytask); Thread t3 = new Thread(mytask); t1.start(); t2.start(); t3.start(); } } //ThreadTest.java 源碼 class MyThread extends Thread { private int ticket = 10 ; public void run() { for ( int i = 0 ; i < 20 ; i++) { if ( this .ticket > 0 ) { System.out.println( this .getName() + " 賣票:ticket" + this .ticket--); } } } } public class ThreadTest { public static void main(String[] args) { // 啟動(dòng)3個(gè)線程t1,t2,t3;每個(gè)線程各賣10張票! MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); t1.start(); t2.start(); t3.start(); } }; |
Thread與Runnable的區(qū)別
Thread 是類,而Runnable是接口;Thread本身是實(shí)現(xiàn)了Runnable接口的類。我們知道“一個(gè)類只能有一個(gè)父類,但是卻能實(shí)現(xiàn)多個(gè)接口”,因此Runnable具有更好的擴(kuò)展性。此外,Runnable還可以用于“資源的共享”。即,多個(gè)線程都是基于某一個(gè)Runnable對(duì)象建立的,它們會(huì)共享Runnable對(duì)象上的資源。通常,建議通過“Runnable”實(shí)現(xiàn)多線程!
Thread的run與start
start() : 它的作用是啟動(dòng)一個(gè)新線程,新線程會(huì)執(zhí)行相應(yīng)的run()方法。start()不能被重復(fù)調(diào)用。start()實(shí)際上是通過本地方法start0()啟動(dòng)線程的。而start0()會(huì)新運(yùn)行一個(gè)線程,新線程會(huì)調(diào)用run()方法。
run() : run()就和普通的成員方法一樣,可以被重復(fù)調(diào)用。單獨(dú)調(diào)用run()的話,會(huì)在當(dāng)前線程中執(zhí)行run(),而并不會(huì)啟動(dòng)新線程!run()就是直接調(diào)用Thread線程的Runnable成員的run()方法,并不會(huì)新建一個(gè)線程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// Demo.java 的源碼 class MyThread extends Thread{ public MyThread(String name) { super (name); } public void run(){ System.out.println(Thread.currentThread().getName()+ " is running" ); } }; public class Demo { public static void main(String[] args) { Thread mythread= new MyThread( "mythread" ); System.out.println(Thread.currentThread().getName()+ " call mythread.run()" ); mythread.run(); System.out.println(Thread.currentThread().getName()+ " call mythread.start()" ); mythread.start(); } } |
輸出:
1
2
3
4
|
main call mythread.run() main is running main call mythread.start() mythread is running |
synchronized
在java中每個(gè)對(duì)象都有一個(gè)同步鎖,當(dāng)我們調(diào)用對(duì)象的synchronized方法就獲取了對(duì)象鎖,synchronized(obj)就獲取了“obj這個(gè)對(duì)象”的同步鎖.不同線程對(duì)同步鎖的訪問是互斥的.某時(shí)間點(diǎn)對(duì)象的同步鎖只能被一個(gè)線程獲取到.通過同步鎖,我們就能在多線程中,實(shí)現(xiàn)對(duì)“對(duì)象/方法”的互斥訪問. 例如,現(xiàn)在有兩個(gè)線程A和線程B,它們都會(huì)訪問“對(duì)象obj的同步鎖”。假設(shè),在某一時(shí)刻,線程A獲取到“obj的同步鎖”并在執(zhí)行一些操作;而此時(shí),線程B也企圖獲取“obj的同步鎖” —— 線程B會(huì)獲取失敗,它必須等待,直到線程A釋放了“該對(duì)象的同步鎖”之后線程B才能獲取到“obj的同步鎖”從而才可以運(yùn)行。
基本規(guī)則
第一條: 當(dāng)一個(gè)線程訪問“某對(duì)象”的“synchronized方法”或者“synchronized代碼塊”時(shí),其他線程對(duì)“該對(duì)象”的該“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。
第二條: 當(dāng)一個(gè)線程訪問“某對(duì)象”的“synchronized方法”或者“synchronized代碼塊”時(shí),其他線程仍然可以訪問“該對(duì)象”的非同步代碼塊。
第三條: 當(dāng)一個(gè)線程訪問“某對(duì)象”的“synchronized方法”或者“synchronized代碼塊”時(shí),其他線程對(duì)“該對(duì)象”的其他的“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。
synchronized 方法
1
2
3
4
5
6
7
8
9
10
|
public synchronized void foo1() { System.out.println( "synchronized methoed" ); } synchronized 代碼塊 public void foo2() { synchronized ( this ) { System.out.println( "synchronized methoed" ); } } |
synchronized代碼塊中的this是指當(dāng)前對(duì)象。也可以將this替換成其他對(duì)象,例如將this替換成obj,則foo2()在執(zhí)行synchronized(obj)時(shí)就獲取的是obj的同步鎖。
synchronized代碼塊可以更精確的控制沖突限制訪問區(qū)域,有時(shí)候表現(xiàn)更高效率
實(shí)例鎖 和 全局鎖
實(shí)例鎖 -- 鎖在某一個(gè)實(shí)例對(duì)象上。如果該類是單例,那么該鎖也具有全局鎖的概念。實(shí)例鎖對(duì)應(yīng)的就是synchronized關(guān)鍵字。
全局鎖 -- 該鎖針對(duì)的是類,無論實(shí)例多少個(gè)對(duì)象,那么線程都共享該鎖。全局鎖對(duì)應(yīng)的就是static synchronized(或者是鎖在該類的class或者classloader對(duì)象上)。
1
2
3
4
5
6
|
pulbic class Something { public synchronized void isSyncA(){} public synchronized void isSyncB(){} public static synchronized void cSyncA(){} public static synchronized void cSyncB(){} } |
(01) x.isSyncA()與x.isSyncB() 不能被同時(shí)訪問。因?yàn)閕sSyncA()和isSyncB()都是訪問同一個(gè)對(duì)象(對(duì)象x)的同步鎖!
(02) x.isSyncA()與y.isSyncA() 可以同時(shí)被訪問。因?yàn)樵L問的不是同一個(gè)對(duì)象的同步鎖,x.isSyncA()訪問的是x的同步鎖,而y.isSyncA()訪問的是y的同步鎖。
(03) x.cSyncA()與y.cSyncB()不能被同時(shí)訪問。因?yàn)閏SyncA()和cSyncB()都是static類型,x.cSyncA()相當(dāng)于Something.isSyncA(),y.cSyncB()相當(dāng)于Something.isSyncB(),因此它們共用一個(gè)同步鎖,不能被同時(shí)反問。
(04) x.isSyncA()與Something.cSyncA() 可以被同時(shí)訪問。因?yàn)閕sSyncA()是實(shí)例方法,x.isSyncA()使用的是對(duì)象x的鎖;而cSyncA()是靜態(tài)方法,Something.cSyncA()可以理解對(duì)使用的是“類的鎖”。因此,它們是可以被同時(shí)訪問的。
線程阻塞與喚醒wait,notify,notifyAll
在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的作用是讓當(dāng)前線程進(jìn)入等待狀態(tài),同時(shí),wait()也會(huì)讓當(dāng)前線程釋放它所持有的鎖。而notify()和notifyAll()的作用,則是喚醒當(dāng)前對(duì)象上的等待線程;notify()是喚醒單個(gè)線程,而notifyAll()是喚醒所有的線程。
Object類中關(guān)于等待/喚醒的API詳細(xì)信息如下:
notify() -- 喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程。
notifyAll() -- 喚醒在此對(duì)象監(jiān)視器上等待的所有線程。
wait() -- 讓當(dāng)前線程處于“等待(阻塞)狀態(tài)”,“直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法”,當(dāng)前線程被喚醒(進(jìn)入“就緒狀態(tài)”)。
wait(long timeout) -- 讓當(dāng)前線程處于“等待(阻塞)狀態(tài)”,“直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法,或者超過指定的時(shí)間量”,當(dāng)前線程被喚醒(進(jìn)入“就緒狀態(tài)”)。
wait(long timeout, int nanos) -- 讓當(dāng)前線程處于“等待(阻塞)狀態(tài)”,“直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法,或者其他某個(gè)線程中斷當(dāng)前線程,或者已超過某個(gè)實(shí)際時(shí)間量”,當(dāng)前線程被喚醒(進(jìn)入“就緒狀態(tài)”)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// WaitTest.java的源碼 class ThreadA extends Thread{ public ThreadA(String name) { super (name); } public void run() { synchronized ( this ) { System.out.println(Thread.currentThread().getName()+ " call notify()" ); // 喚醒當(dāng)前的wait線程 notify(); } } } public class WaitTest { public static void main(String[] args) { ThreadA t1 = new ThreadA( "t1" ); synchronized (t1) { try { // 啟動(dòng)“線程t1” System.out.println(Thread.currentThread().getName()+ " start t1" ); t1.start(); // 主線程等待t1通過notify()喚醒。 System.out.println(Thread.currentThread().getName()+ " wait()" ); t1.wait(); System.out.println(Thread.currentThread().getName()+ " continue" ); } catch (InterruptedException e) { e.printStackTrace(); } } } } |
輸出
1
2
3
4
|
main start t1 main wait() t1 call notify() main continue |
(01) 注意,圖中"主線程" 代表“主線程main”。"線程t1" 代表WaitTest中啟動(dòng)的“線程t1”。 而“鎖” 代表“t1這個(gè)對(duì)象的同步鎖”。
(02) “主線程”通過 new ThreadA("t1") 新建“線程t1”。隨后通過synchronized(t1)獲取“t1對(duì)象的同步鎖”。然后調(diào)用t1.start()啟動(dòng)“線程t1”。
(03) “主線程”執(zhí)行t1.wait() 釋放“t1對(duì)象的鎖”并且進(jìn)入“等待(阻塞)狀態(tài)”。等待t1對(duì)象上的線程通過notify() 或 notifyAll()將其喚醒。
(04) “線程t1”運(yùn)行之后,通過synchronized(this)獲取“當(dāng)前對(duì)象的鎖”;接著調(diào)用notify()喚醒“當(dāng)前對(duì)象上的等待線程”,也就是喚醒“主線程”。
(05) “線程t1”運(yùn)行完畢之后,釋放“當(dāng)前對(duì)象的鎖”。緊接著,“主線程”獲取“t1對(duì)象的鎖”,然后接著運(yùn)行。
t1.wait()是通過“線程t1”調(diào)用的wait()方法,但是調(diào)用t1.wait()的地方是在“主線程main”中。而主線程必須是“當(dāng)前線程”,也就是運(yùn)行狀態(tài),才可以執(zhí)行t1.wait()。所以,此時(shí)的“當(dāng)前線程”是“主線程main”!因此,t1.wait()是讓“主線程”等待,而不是“線程t1”!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
package thread.Test; public class NotifyAllTest { private static Object obj = new Object(); public static void main(String[] args) { ThreadA t1 = new ThreadA( "t1" ); ThreadA t2 = new ThreadA( "t2" ); ThreadA t3 = new ThreadA( "t3" ); t1.start(); t2.start(); t3.start(); try { System.out.println(Thread.currentThread().getName()+ " sleep(3000)" ); Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj) { System.out.println(Thread.currentThread().getName()+ " notifyAll()" ); obj.notifyAll(); //在此喚醒t1.t2.t3 } } static class ThreadA extends Thread{ public ThreadA(String name){ super (name); } public void run() { synchronized (obj) { try { // 打印輸出結(jié)果 System.out.println(Thread.currentThread().getName() + " wait" ); //釋放obj對(duì)象鎖 obj.wait(); // 打印輸出結(jié)果 System.out.println(Thread.currentThread().getName() + " continue" ); } catch (InterruptedException e) { e.printStackTrace(); } } } } } |
輸出:
1
2
3
4
5
6
7
8
|
t1 wait main sleep(3000) t3 wait t2 wait main notifyAll() t2 continue t3 continue t1 continue |
(01) 主線程中新建并且啟動(dòng)了3個(gè)線程"t1", "t2"和"t3"。
(02) 主線程通過sleep(3000)休眠3秒。在主線程休眠3秒的過程中,我們假設(shè)"t1", "t2"和"t3"這3個(gè)線程都運(yùn)行了。以"t1"為例,當(dāng)它運(yùn)行的時(shí)候,它會(huì)執(zhí)行obj.wait()等待其它線程通過notify()或額nofityAll()來喚醒它;相同的道理,"t2"和"t3"也會(huì)等待其它線程通過nofity()或nofityAll()來喚醒它們。
(03) 主線程休眠3秒之后,接著運(yùn)行。執(zhí)行 obj.notifyAll() 喚醒obj上的等待線程,即喚醒"t1", "t2"和"t3"這3個(gè)線程。 緊接著,主線程的synchronized(obj)運(yùn)行完畢之后,主線程釋放“obj鎖”。這樣,"t1", "t2"和"t3"就可以獲取“obj鎖”而繼續(xù)運(yùn)行了!
notify,notifyall與鎖的關(guān)系
Object中的wait(), notify()等函數(shù),和synchronized一樣,會(huì)對(duì)“對(duì)象的同步鎖”進(jìn)行操作。
wait()會(huì)使“當(dāng)前線程”等待,因?yàn)榫€程進(jìn)入等待狀態(tài),所以線程應(yīng)該釋放它鎖持有的“同步鎖”,否則其它線程獲取不到該“同步鎖”而無法運(yùn)行!
OK,線程調(diào)用wait()之后,會(huì)釋放它鎖持有的“同步鎖”;而且,根據(jù)前面的介紹,我們知道:等待線程可以被notify()或notifyAll()喚醒。現(xiàn)在,請(qǐng)思考一個(gè)問題:notify()是依據(jù)什么喚醒等待線程的?或者說,wait()等待線程和notify()之間是通過什么關(guān)聯(lián)起來的?答案是:依據(jù)“對(duì)象的同步鎖”。
負(fù)責(zé)喚醒等待線程的那個(gè)線程(我們稱為“喚醒線程”),它只有在獲取“該對(duì)象的同步鎖”(這里的同步鎖必須和等待線程的同步鎖是同一個(gè)),并且調(diào)用notify()或notifyAll()方法之后,才能喚醒等待線程。雖然,等待線程被喚醒;但是,它不能立刻執(zhí)行,因?yàn)閱拘丫€程還持有“該對(duì)象的同步鎖”。必須等到喚醒線程釋放了“對(duì)象的同步鎖”之后,等待線程才能獲取到“對(duì)象的同步鎖”進(jìn)而繼續(xù)運(yùn)行。
總之,notify(), wait()依賴于“同步鎖”,而“同步鎖”是對(duì)象鎖持有,并且每個(gè)對(duì)象有且僅有一個(gè)!這就是為什么notify(), wait()等函數(shù)定義在Object類,而不是Thread類中的原因。
線程讓步y(tǒng)ield
線程讓步,使線程從執(zhí)行狀態(tài)變?yōu)榫途w狀態(tài),從而讓其它具有相同優(yōu)先級(jí)的等待線程獲取執(zhí)行權(quán);但是,并不能保證在當(dāng)前線程調(diào)用yield()之后,其它具有相同優(yōu)先級(jí)的線程就一定能獲得執(zhí)行權(quán);也有可能是當(dāng)前線程又進(jìn)入到“運(yùn)行狀態(tài)”繼續(xù)運(yùn)行。
yield 與wait
(01) wait()是讓線程由“運(yùn)行狀態(tài)”進(jìn)入到“等待(阻塞)狀態(tài)”,而不yield()是讓線程由“運(yùn)行狀態(tài)”進(jìn)入到“就緒狀態(tài)”。
(02) wait()是會(huì)線程釋放它所持有對(duì)象的同步鎖,而yield()方法不會(huì)釋放鎖。
(03) wait是object的方法,yield是Thread的方法
線程休眠 sleep
sleep() 的作用是讓當(dāng)前線程休眠,即當(dāng)前線程會(huì)從“運(yùn)行狀態(tài)”進(jìn)入到“休眠(阻塞)狀態(tài)”。sleep()會(huì)指定休眠時(shí)間,線程休眠的時(shí)間會(huì)大于/等于該休眠時(shí)間;在線程重新被喚醒時(shí),它會(huì)由“阻塞狀態(tài)”變成“就緒狀態(tài)”,從而等待cpu的調(diào)度執(zhí)行。
sleep與wait的區(qū)別
wait()的作用是讓當(dāng)前線程由“運(yùn)行狀態(tài)”進(jìn)入“等待(阻塞)狀態(tài)”的同時(shí),也會(huì)釋放同步鎖。而sleep()的作用是也是讓當(dāng)前線程由“運(yùn)行狀態(tài)”進(jìn)入到“休眠(阻塞)狀態(tài)。(這個(gè)其實(shí)區(qū)別不大)
wait()會(huì)釋放對(duì)象的同步鎖,而sleep()則不會(huì)釋放鎖
wait是object的方法,sleep是Thread的方法
join
讓主線程等待,子線程運(yùn)行完畢,主線程才能繼續(xù)運(yùn)行
interrupt
用來終止處于阻塞狀態(tài)的線程
1
2
3
4
5
6
7
8
9
10
|
@Override public void run() { try { while ( true ) { // 執(zhí)行任務(wù)... } } catch (InterruptedException ie) { // 由于產(chǎn)生InterruptedException異常,退出while(true)循環(huán),線程終止! } } |
在while(true)中不斷的執(zhí)行任務(wù),當(dāng)線程處于阻塞狀態(tài)時(shí),調(diào)用線程的interrupt()產(chǎn)生InterruptedException中斷。中斷的捕獲在while(true)之外,這樣就退出了while(true)循環(huán)
終止處于運(yùn)行狀態(tài)的線程
1
2
3
4
5
6
|
@Override public void run() { while (!isInterrupted()) { // 執(zhí)行任務(wù)... } } |
通用的終止線程的方式
1
2
3
4
5
6
7
8
9
10
11
|
@Override public void run() { try { // 1. isInterrupted()保證,只要中斷標(biāo)記為true就終止線程。 while (!isInterrupted()) { // 執(zhí)行任務(wù)... } } catch (InterruptedException ie) { // 2. InterruptedException異常保證,當(dāng)InterruptedException異常產(chǎn)生時(shí),線程被終止。 } } |
線程優(yōu)先級(jí)
java 中的線程優(yōu)先級(jí)的范圍是1~10,默認(rèn)的優(yōu)先級(jí)是5。“高優(yōu)先級(jí)線程”會(huì)優(yōu)先于“低優(yōu)先級(jí)線程”執(zhí)行。java 中有兩種線程:用戶線程和守護(hù)線程。可以通過isDaemon()方法來區(qū)別它們:如果返回false,則說明該線程是“用戶線程”;否則就是“守護(hù)線程”。用戶線程一般用戶執(zhí)行用戶級(jí)任務(wù),而守護(hù)線程也就是“后臺(tái)線程”,一般用來執(zhí)行后臺(tái)任務(wù)。需要注意的是:Java虛擬機(jī)在“用戶線程”都結(jié)束后會(huì)后退出。
每個(gè)線程都有一個(gè)優(yōu)先級(jí)。“高優(yōu)先級(jí)線程”會(huì)優(yōu)先于“低優(yōu)先級(jí)線程”執(zhí)行。每個(gè)線程都可以被標(biāo)記為一個(gè)守護(hù)進(jìn)程或非守護(hù)進(jìn)程。在一些運(yùn)行的主線程中創(chuàng)建新的子線程時(shí),子線程的優(yōu)先級(jí)被設(shè)置為等于“創(chuàng)建它的主線程的優(yōu)先級(jí)”,當(dāng)且僅當(dāng)“創(chuàng)建它的主線程是守護(hù)線程”時(shí)“子線程才會(huì)是守護(hù)線程”。
當(dāng)Java虛擬機(jī)啟動(dòng)時(shí),通常有一個(gè)單一的非守護(hù)線程(該線程通過是通過main()方法啟動(dòng))。JVM會(huì)一直運(yùn)行直到下面的任意一個(gè)條件發(fā)生,JVM就會(huì)終止運(yùn)行:
(01) 調(diào)用了exit()方法,并且exit()有權(quán)限被正常執(zhí)行。
(02) 所有的“非守護(hù)線程”都死了(即JVM中僅僅只有“守護(hù)線程”)。
守護(hù)進(jìn)程
(01) 主線程main是用戶線程,它創(chuàng)建的子線程t1也是用戶線程。
(02) t2是守護(hù)線程。在“主線程main”和“子線程t1”(它們都是用戶線程)執(zhí)行完畢,只剩t2這個(gè)守護(hù)線程的時(shí)候,JVM自動(dòng)退出。
生產(chǎn)者消費(fèi)者問題
(01) 生產(chǎn)者僅僅在倉儲(chǔ)未滿時(shí)候生產(chǎn),倉滿則停止生產(chǎn)。
(02) 消費(fèi)者僅僅在倉儲(chǔ)有產(chǎn)品時(shí)候才能消費(fèi),倉空則等待。
(03) 當(dāng)消費(fèi)者發(fā)現(xiàn)倉儲(chǔ)沒產(chǎn)品可消費(fèi)時(shí)候會(huì)通知生產(chǎn)者生產(chǎn)。
(04) 生產(chǎn)者在生產(chǎn)出可消費(fèi)產(chǎn)品時(shí)候,應(yīng)該通知等待的消費(fèi)者去消費(fèi)。
線程之間的通信
方式:共享內(nèi)存和消息傳遞
共享內(nèi)存:線程A和線程B共享內(nèi)存,線程A更新共享變量的值,刷新到主內(nèi)存中,線程B去主內(nèi)存中讀取線程A更新后的變量。整個(gè)通信過程必須通過主內(nèi)存。同步是顯式進(jìn)行的。
如果一個(gè)變量是volatile類型,則對(duì)該變量的讀寫就將具有原子性。如果是多個(gè)volatile操作或類似于volatile++這種復(fù)合操作,這些操作整體上不具有原子性。
volatile變量自身具有下列特性:
[可見性]:對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫入。
[原子性]:對(duì)任意單個(gè)volatile變量的讀/寫具有原子性,但類似于volatile++這種復(fù)合操作不具有原子性。
volatile寫:當(dāng)寫一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存。
volatile讀:當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。
消息傳遞:消息的發(fā)送在消息的接受之前,同步隱式進(jìn)行。
ThreadLocal
ThreadLocal 不是用來解決共享對(duì)象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對(duì)象是該線程自己使用的對(duì)象,其他線程是不需要訪問的,也訪問不到的.ThreadLocal使得各線程能夠保持各自獨(dú)立的一個(gè)對(duì)象,并不是通過ThreadLocal.set()來實(shí)現(xiàn)的,而是通過每個(gè)線程中的new 對(duì)象 的操作來創(chuàng)建的對(duì)象,每個(gè)線程創(chuàng)建一個(gè),不是什么對(duì)象的拷貝或副本。通過ThreadLocal.set()將這個(gè)新創(chuàng)建的對(duì)象的引用保存到各線程的自己的一個(gè)map中,每個(gè)線程都有這樣一個(gè)map,執(zhí)行ThreadLocal.get()時(shí),各線程從自己的map中取出放進(jìn)去的對(duì)象,因此取出來的是各自自己線程中的對(duì)象,ThreadLocal實(shí)例是作為map的key來使用的。 如果ThreadLocal.set()進(jìn)去的東西本來就是多個(gè)線程共享的同一個(gè)對(duì)象,那么多個(gè)線程的ThreadLocal.get()取得的還是這個(gè)共享對(duì)象本身,還是有并發(fā)訪問問題。
ThreadLocal的一個(gè)實(shí)現(xiàn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * 使用了ThreadLocal的類 * * @author leizhimin 2010-1-5 10:35:27 */ public class MyThreadLocal { //定義了一個(gè)ThreadLocal變量,用來保存int或Integer數(shù)據(jù) private com.lavasoft.test2.ThreadLocal<Integer> tl = new com.lavasoft.test2.ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0 ; } }; public Integer getNextNum() { //將tl的值獲取后加1,并更新設(shè)置t1的值 tl.set(tl.get() + 1 ); return tl.get(); } } class ThreadLocal<T> { private Map<Thread, T> map = Collections.synchronizedMap( new HashMap<Thread, T>()); public ThreadLocal() { } protected T initialValue() { return null ; } public T get() { Thread t = Thread.currentThread(); T obj = map.get(t); if (obj == null && !map.containsKey(t)) { obj = initialValue(); map.put(t, obj); } return obj; } public void set(T value) { map.put(Thread.currentThread(), value); } public void remove() { map.remove(Thread.currentThread()); } } |
事實(shí)上ThreadLocal是這樣做的:
1
2
3
4
5
6
7
8
9
10
|
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) { ThreadLocalMap.Entry e = map.getEntry( this ); if (e != null ) return (T)e.value; } return setInitialValue(); } |