前言
在上篇文章Java并發(fā)編程之線程篇之線程中斷(三)中我們講解了線程中斷的相關(guān)知識(shí)點(diǎn),現(xiàn)在我們來(lái)了解一下線程間的通信。線程間的通信在我們實(shí)際項(xiàng)目中是不可或缺的,多數(shù)情況下,我們需要?jiǎng)?chuàng)建多個(gè)線程,配合完成某項(xiàng)任務(wù)。合理并正確使用線程間的通信方式,是作為一個(gè)良好程序員必須掌握的技能。那現(xiàn)在就讓我們來(lái)了解在Java中線程間通信的處理方式吧!閱讀該篇文章你可能需要具備一下知識(shí)點(diǎn):
- Java并發(fā)編程之Java內(nèi)存模型(一)
- Java并發(fā)編程之Volatile(二)
- Java并發(fā)編程之Synchronized(三)
- Java并發(fā)編程之鎖機(jī)制之Lock接口(七)
- Java并發(fā)編程之鎖機(jī)制之AQS(AbstractQueuedSynchronizer)(八)
- Java并發(fā)編程之鎖機(jī)制之LockSupport工具(九)
- Java并發(fā)編程之鎖機(jī)制之Condition接口(十)
- Java并發(fā)編程之鎖機(jī)制之(ReentrantLock)重入鎖(十一)
線程的狀態(tài)
在了解線程的通信知識(shí)之前,我們需要了解線程的狀態(tài),熟悉線程的狀態(tài),不僅有助于我們更好的排查多線程項(xiàng)目出現(xiàn)的死鎖、線程安全等問(wèn)題,還能更好的讓我們分析與理解線程間的通信。下面就讓我們來(lái)看一下線程有哪些狀態(tài)吧!
在Java中線程的有以下5種狀態(tài),如下所示:
| 線程狀態(tài) | 含義 |
|---|---|
| NEW | 新建狀態(tài),線程已經(jīng)創(chuàng)建,但是沒(méi)有執(zhí)行start()方法 |
| RUNNABLE | 可運(yùn)行狀態(tài),線程可以在JVM中運(yùn)行,但是還需要等待CPU分配資源 |
| BLOCKED | 阻塞狀態(tài),當(dāng)遇到synchronized且沒(méi)有取得相應(yīng)的鎖,就會(huì)進(jìn)入這個(gè)狀態(tài) |
| WAITING | 等待狀態(tài),當(dāng)線程中wait()/join/Locksupport.park方法時(shí),就會(huì)進(jìn)入這個(gè)狀態(tài) |
| TIMED_WAITING | 計(jì)時(shí)等待狀態(tài),當(dāng)調(diào)用Thread.sleep()或者Object.wait(xx)或者Thread.join(xx)或者LockSupport.parkNanos或者LockSupport.partUntil時(shí),進(jìn)入該狀態(tài) |
| TERMINATED | 線程中斷狀態(tài),線程被中斷或者運(yùn)行結(jié)束,就會(huì)進(jìn)入這個(gè)狀態(tài) |
在上述表格中,線程的5種狀態(tài)對(duì)應(yīng)著Java的不同方法,具體如下圖所示:

需要注意的是,在上圖中
標(biāo)紅的兩個(gè)狀態(tài),是操作系統(tǒng)中線程對(duì)應(yīng)的狀態(tài),Java將這兩種狀態(tài)合并為可運(yùn)行狀態(tài)(RUNNABLE)。在操作系統(tǒng)中就緒狀態(tài)(READY)表示線程已經(jīng)準(zhǔn)備完畢,等待CPU分配時(shí)間片。運(yùn)行中狀態(tài)(RUNNING)表示當(dāng)線程分到時(shí)間片,線程開(kāi)始正式執(zhí)行。
volatile的使用
在Java內(nèi)存模型中,我們?cè)岬竭^(guò),為了提示程序的運(yùn)行速度,Java將內(nèi)存分為了工作內(nèi)存(線程獨(dú)占,不與其他線程共享)與主內(nèi)存。當(dāng)多個(gè)線程同時(shí)訪問(wèn)同一個(gè)對(duì)象或者變量的時(shí)候,由于每個(gè)線程都需要將該對(duì)象或變量拷貝到自己的工作內(nèi)存中。又因?yàn)榫€程的工作內(nèi)存是私有且不與其他線程共享的。那么當(dāng)一線程修改變量的值后,會(huì)導(dǎo)致對(duì)其他線程不可見(jiàn)。Java內(nèi)存模型如下圖所示:

為了保證數(shù)據(jù)的可見(jiàn)性。Java提供了volatile關(guān)鍵字。volatile關(guān)鍵字修飾變量,就是告知線程對(duì)該變量的訪問(wèn)必須重主內(nèi)存中獲取。而對(duì)它的改變必須同步刷新到主內(nèi)存中。這樣就能保證線程對(duì)變量訪問(wèn)的可見(jiàn)性。關(guān)于volatile的使用,參看如下例子:
class VolatileDemo {
int a = 1;
int b = 2;
public void change() {
a = 3;
b = 4;
}
public void print(String threadName) {
System.out.println(threadName + "--->" + "a = " + a + ";b = " + b);
}
public static void main(String[] args) {
final VolatileDemo volatileDemo = new VolatileDemo();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatileDemo.change();
}
}).start();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatileDemo.print(Thread.currentThread().getName());
}
}).start();
}
}
}
程序輸出結(jié)果:
Thread-1--->a = 1;b = 2 //錯(cuò)誤
Thread-3--->a = 1;b = 2 //錯(cuò)誤
Thread-2--->a = 1;b = 2 //錯(cuò)誤
Thread-5--->a = 3;b = 4
Thread-4--->a = 3;b = 4
Thread-6--->a = 3;b = 4
Thread-7--->a = 3;b = 4
Thread-8--->a = 3;b = 4
....省略其他
在上述代碼中,如果我們不采用volatile關(guān)鍵字修飾a,b變量,那么會(huì)導(dǎo)致其他線程仍然獲取的是自己本身工作內(nèi)存中的a、b變量的值。為了保證訪問(wèn)公共變量對(duì)其他線程的可見(jiàn)性,我們需要將變量通過(guò)volatile來(lái)修飾。修改我們的代碼:
volatile int a = 1;
volatile int b = 2;
采用volatile修飾,輸出結(jié)果如下
Thread-2--->a = 3;b = 4
Thread-1--->a = 3;b = 4
Thread-6--->a = 3;b = 4
Thread-3--->a = 3;b = 4
Thread-4--->a = 3;b = 4
Thread-9--->a = 3;b = 4
Thread-10--->a = 3;b = 4
....省略其他
需要注意的是volatile只對(duì)單次的變量的操作具對(duì)其他線程有可見(jiàn)性,對(duì)應(yīng)類似于a++,這種操作線讀取a變量的值,進(jìn)行運(yùn)算后再重新對(duì)變量賦值的操作,仍然會(huì)出現(xiàn)線程安全的問(wèn)題。對(duì)關(guān)于volatile的更多介紹,大家可以查看Java并發(fā)編程之Volatile(二)。
synchronized的使用
除了使用volatile實(shí)現(xiàn)線程的通信之外,我們還可以使用synchronized及Object中的配套方法wait()/notify()、wait()/notifyAll來(lái)實(shí)現(xiàn)線程的通信,在了解具體的實(shí)現(xiàn)之前,我們先來(lái)了解synchronized關(guān)鍵字在Java中的作用。
關(guān)鍵字synchronized可以修飾方法或者代碼塊,使用synchronized可以確保多個(gè)線程在同一時(shí)刻,只能有一個(gè)線程處于方法或者同步塊中,它保證了線程對(duì)變量訪問(wèn)的可見(jiàn)性和排他性。如下代碼所示:
public class SyncCodeBlock {
public int i;
public void syncTask(){
//同步代碼庫(kù)
synchronized (this){
i++;
}
}
}
然后我們通過(guò)javap指令反編譯得到字節(jié)碼。來(lái)繼續(xù)分析synchronized關(guān)鍵字的實(shí)現(xiàn)細(xì)節(jié),如下所示:
//===========主要看看syncTask方法實(shí)現(xiàn)================
public void syncTask();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter //注意此處,進(jìn)入同步方法,表示獲取到鎖
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
15: monitorexit //注意此處,退出同步方法,釋放鎖
16: goto 24
19: astore_2
20: aload_1
21: monitorexit //注意此處,退出同步方法
22: aload_2
23: athrow
24: return
Exception table:
//省略其他字節(jié)碼…….
}
在上述字節(jié)碼信息中,對(duì)于同步塊的實(shí)現(xiàn)使用了monitorenter和monitorexit兩個(gè)指令,其本質(zhì)原理是對(duì)某個(gè)對(duì)象的監(jiān)視器(monitor)的獲取。而這個(gè)獲取過(guò)程是排他的,也就是同一時(shí)刻只能有一個(gè)線程,獲取到有synchronized所保護(hù)對(duì)象的監(jiān)視器。在Java中,任何一個(gè)對(duì)象都有自己的監(jiān)視器(monitor),當(dāng)這個(gè)對(duì)象有同步塊或者這個(gè)對(duì)象的同步方法調(diào)用的時(shí)候,執(zhí)行方法的線程必須先獲取到該對(duì)象的監(jiān)視器才能進(jìn)入同步塊,或者同步方法,而沒(méi)有獲取到監(jiān)視器(monitor)的線程會(huì)阻塞在同步塊或者同步方法的入口,進(jìn)入BLOCKED狀態(tài)。如下圖所示:

從上圖中,我們可以得出,任意線程在對(duì)synchronized關(guān)鍵字修飾的Object進(jìn)行訪問(wèn)的時(shí)候,首先要獲得Object的監(jiān)視器(monitor),如果獲取失敗,線程進(jìn)入同步隊(duì)列,且線程狀態(tài)變?yōu)?code>BLOCKED。當(dāng)訪問(wèn)Object的前驅(qū)線程(moniterenter成功的線程)釋放了Object的監(jiān)視器(monitorexit)。則喚醒阻塞在同步隊(duì)列中的線程,使其嘗試對(duì)監(jiān)視器的獲取。其中對(duì)監(jiān)視器的獲取與釋放,我們一般稱之為獲取鎖與釋放鎖。在下文中我們都用獲取鎖與釋放鎖來(lái)表示這兩個(gè)過(guò)程。
關(guān)于synchronized下同步隊(duì)列的知識(shí)點(diǎn)補(bǔ)充:
synchronized在JVM中實(shí)現(xiàn)的鎖機(jī)制是基于同步隊(duì)列與等待隊(duì)列的,這與courrent包下的Lock接口下的鎖機(jī)制的實(shí)現(xiàn)方式十分類似。需要注意的是,在synchronized中wait()后的線程會(huì)進(jìn)入一個(gè)FIFO的隊(duì)列(同步隊(duì)列)中,notify()/notifyAll()是一個(gè)有序的出隊(duì)列的過(guò)程。
synchronized下的等待/通知機(jī)制實(shí)現(xiàn)
在上文中,我們提到如果使用synchronized來(lái)實(shí)現(xiàn)線程間的通信,我們需要結(jié)合Object中的配套方法wait()/notify()、wait()/notifyAll。我們先來(lái)看看一看Obejet中這系列方法的說(shuō)明:
| 方法名稱 | 描述 |
|---|---|
| wait() | 調(diào)用該方法的線程進(jìn)入WAITING狀態(tài),只有等待另外線程的通知或被中斷才會(huì)返回,需要注意,線程調(diào)用wait()方法前,需要獲得對(duì)象的監(jiān)視器。當(dāng)調(diào)用wait()方法后,會(huì)釋放對(duì)象的監(jiān)視器 |
| wait(long) | 調(diào)用該方法的線程進(jìn)入TIMED_WAITING狀態(tài),這里的參數(shù)時(shí)間是毫秒,等待對(duì)應(yīng)毫秒事件,如果沒(méi)有收到其他線程通知,則超時(shí)返回 |
| wait(long,int) | 調(diào)用該方法的線程進(jìn)入TIMED_WAITING狀態(tài),基本作用同wiat(long),第二個(gè)參數(shù)代表為納秒,也就是等待時(shí)間為毫秒+納秒。 |
| notify() | 通知一個(gè)在對(duì)象監(jiān)視器上等待的線程,使其從wait()方法返回,而返回的前提是該線程獲取到了對(duì)象的監(jiān)視器。 |
| notifyAll() | 通知所有在監(jiān)視器上等待的線程,具體喚醒那個(gè)線程由CPU決定 |
使用Object的wait()/notify()、wait()/notifyall(),其實(shí)是我們經(jīng)常使用的等待/通知機(jī)制,所謂的等待/通知機(jī)制是指一個(gè)線程A調(diào)用了對(duì)象O的wait()方法進(jìn)入等待狀態(tài),而另一個(gè)線程B調(diào)用了對(duì)象O的notify或者notifyAll方法。線程A收到通知后從對(duì)象O的wait()方法返回,進(jìn)而執(zhí)行后續(xù)的操作。
下面,我們來(lái)通過(guò)一個(gè)例子來(lái)了解使用synchronized完成線程的通信,具體例子如下所示:
class SynchronizedDemo {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(new WaitRunnable(), "WaitThread").start();
TimeUnit.SECONDS.sleep(1);//這里睡眠,是保證Wait線程先執(zhí)行
new Thread(new NotifyRunnable(), "NotifyThread").start();
}
static class WaitRunnable implements Runnable {
@Override
public void run() {
synchronized (lock) {
while (flag) {//注意,通過(guò)while循環(huán)來(lái)判斷條件
String name = Thread.currentThread().getName();
try {
System.out.println(name + "--->wait in " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "--->wake up in " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
}
static class NotifyRunnable implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (lock) {
System.out.println(name + "--->notify all in " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
}
/**
* 這里再次加鎖,是為了驗(yàn)證當(dāng)調(diào)用對(duì)象的notifyAll方法時(shí),
* 如果線程不執(zhí)行monitorexit(也就是釋放鎖),那么是不會(huì)喚醒其他線程的
*/
synchronized (lock) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(name + "--->hold lock again in " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
輸出結(jié)果如下:
WaitThread--->wait in 23:10:11
NotifyThread--->notify all in 23:10:12
NotifyThread--->hold lock again in 23:10:14
WaitThread--->wake up in 23:10:14
從上文中,我們可以得出以下結(jié)論:
- 使用wait()、notify()和notifyAll時(shí)需要先獲取對(duì)象的監(jiān)視器(執(zhí)行monitorenter指令成功)
- 調(diào)用wait()方法后,線程狀態(tài)由RUNNING變?yōu)閃AITING,并將該線程加入等待隊(duì)列。
- notify()或notifyAll()方法調(diào)用后,等待線程依舊不會(huì)從wait()返回,需要調(diào)用notify()或notfifyAll()的線程釋放對(duì)象的監(jiān)視器(也就是執(zhí)行monitorexit指令)后,等待線程才會(huì)有機(jī)會(huì)從wait()返回。
- notify()方法將等待隊(duì)列中的一個(gè)等待線程從等待隊(duì)列移到同步隊(duì)列中,而notifyAll()方法則是將等待隊(duì)列中所有的線程全部移動(dòng)到同步隊(duì)列,被移動(dòng)的線程狀態(tài)由WAITING變?yōu)锽LOCKED。
- 從wait()方法返回的前提是獲得了調(diào)用對(duì)象的監(jiān)視器(執(zhí)行monitorenter指令成功)。
Lock下的等待/通知機(jī)制實(shí)現(xiàn)
除了使用synchronized完成線程的通信之外,我們還可以使用courrent包下的Lock接口,這里以ReentrantLock為例。具體例子如下所示:
class LockDemo {
static boolean flag = true;
static Lock lock = new ReentrantLock();
static Condition codition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(new WaitRunnable(), "WaitThread").start();
TimeUnit.SECONDS.sleep(1);//這里睡眠,是保證Wait線程先執(zhí)行
new Thread(new NotifyRunnable(), "NotifyThread").start();
}
static class WaitRunnable implements Runnable {
@Override
public void run() {
lock.lock();
try {
while (flag) {
String name = Thread.currentThread().getName();
System.out.println(name + "--->wait in " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
codition.await();
System.out.println(name + "--->wake up in " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
static class NotifyRunnable implements Runnable {
@Override
public void run() {
lock.lock();
try {
String name = Thread.currentThread().getName();
System.out.println(name + "--->notify all in " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
flag = false;
codition.signalAll();
} finally {
lock.unlock();
}
}
}
}
輸出結(jié)果:
WaitThread--->wait in 23:39:34
NotifyThread--->notify all in 23:39:35
WaitThread--->wake up in 23:39:35
關(guān)于Lock使用及原理,大家可以查看以下幾篇文章,這里就不再進(jìn)行分析了。
- Java并發(fā)編程之鎖機(jī)制之Lock接口(七)
- Java并發(fā)編程之鎖機(jī)制之AQS(AbstractQueuedSynchronizer)(八)
- Java并發(fā)編程之鎖機(jī)制之LockSupport工具(九)
- Java并發(fā)編程之鎖機(jī)制之Condition接口(十)
- Java并發(fā)編程之鎖機(jī)制之(ReentrantLock)重入鎖(十一)
等待/通知的經(jīng)典范式
從上方的例子中,我們可以總結(jié)并得到非常經(jīng)典的等待/通知方式,該范式分別針對(duì)等待方法(消費(fèi)者)和通知方(生產(chǎn)者)。
等待方
等待方遵循如下原則:
- 獲取對(duì)象的鎖。
- 如果條件不滿足,那么條件不滿足,那么調(diào)用對(duì)象的wait()方法。被通知后仍然要繼續(xù)檢查條件。
- 條件滿足則執(zhí)行相應(yīng)邏輯。
對(duì)應(yīng)偽代碼分別如下:
使用synchronized方式:
synchronized(對(duì)象) {
while(條件不滿足){
對(duì)象.wait();
}
對(duì)應(yīng)的處理邏輯
}
使用lock方式:
lock.lock();
try{
while(條件不滿足){
condition.wait();
}
}finally{
lock.unlock();
}
通知方代碼
通知方遵循如下原則:
- 獲得對(duì)象的鎖
- 改變條件
- 通知所有等待在對(duì)象上的線程。
對(duì)應(yīng)偽代碼分別如下:
使用synchronized方式:
synchronized(對(duì)象){
改變條件
對(duì)象.notifyAll()
}
使用lock方式:
lock.lock();
try{
改變條件
condition.singleAll();
}finally{
lock.unlock();
}
Thread.join的使用
除了使用上面我們介紹的經(jīng)典范式以外,我們還可以使用Thread.join()方法。join方法的使用含義如下:
當(dāng)線程A調(diào)用線程B對(duì)象(bThread)的join方法,其含義是當(dāng)前線程A等待線程B終止后,才從線程A中bThread.join()代碼的調(diào)用處返回。線程除了join方法以外還提供了join(long millis)和void join(long millis, int nanos)這兩個(gè)具備超時(shí)特性的方法。這兩個(gè)方法的意義是如果在給定的時(shí)間內(nèi)線程B沒(méi)有終止。那么線程A將會(huì)從該方法中返回。下面我們來(lái)看一下join方法的使用例子,如下所示:
class AThread extends Thread {
public AThread() {
super("[AThread]”);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "-->start”);
try {
for (int i = 0; i < 5; i++) {
System.out.println(threadName + "loop at" + i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println(threadName + "--->end”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class BThread extends Thread {
private AThread mAThread;
public BThread(AThread aThread) {
super("[BThread]”);
this.mAThread = aThread;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "-->start”);
try {
mAThread.join();//使B線程等待,需要等待A線程執(zhí)行完畢后,才能繼續(xù)執(zhí)行
System.out.println(threadName + "--->end”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadJoinDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "-->start”);
AThread aThread = new AThread();
BThread bThread = new BThread(aThread);
try {
aThread.start();
TimeUnit.SECONDS.sleep(1);
bThread.start();
aThread.join();//主線程等待A線程執(zhí)行完畢后,才繼續(xù)執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->end”);
}
}
在上述例子中,我們主要實(shí)現(xiàn)以下兩個(gè)效果:
-
主線程(main線程)等待A線程執(zhí)行完畢后,才繼續(xù)執(zhí)行 -
B線程等待A線程執(zhí)行完畢后,才繼續(xù)執(zhí)行。
我們查看輸出結(jié)果:
main-->start //main線程啟動(dòng)
[AThread]-->start //A線程啟動(dòng)
[AThread]loop at0 //A線程開(kāi)始執(zhí)行循環(huán)
[AThread]loop at1
[BThread]-->start //B線程開(kāi)始啟動(dòng),因?yàn)樵贐線程中調(diào)用了aThread.join()那么B線程會(huì)等待A線程執(zhí)行完畢后,才開(kāi)始執(zhí)行
[AThread]loop at2 //A線程繼續(xù)執(zhí)行
[AThread]loop at3
[AThread]loop at4
[AThread]--->end //A線程執(zhí)行完畢后,
[BThread]--->end //A線程執(zhí)行完畢后,喚醒B線程繼續(xù)執(zhí)行
main--->end //主線程執(zhí)行完畢
整個(gè)程序是按照我們之前的邏輯在運(yùn)行,下面我們來(lái)查看線程中join方法的實(shí)現(xiàn)原理,具體代碼如下所示:
join()方法內(nèi)部會(huì)調(diào)用join(final long millis)方法。
//同步方法默認(rèn)的鎖為調(diào)用該方法的對(duì)象,也就是xxThread.join()的xxThread
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {//如果等待時(shí)間大于0
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {//如果等待時(shí)間為0,
while (isAlive()) {
wait(0);//當(dāng)前線程存活,那么會(huì)使當(dāng)前線程等待(當(dāng)前線程指運(yùn)行xxThread.join的線程,而不是xxThread)
}
} else {
throw new IllegalArgumentException("timeout value is negative”);
}
}
我們簡(jiǎn)單的分析一下代碼,當(dāng)B線程調(diào)用A線程的join()方法時(shí),當(dāng)前鎖對(duì)象為A線程。在join()方法內(nèi)部會(huì)調(diào)用wait(0),該方法會(huì)使B線程等待。只有當(dāng)A線程執(zhí)行完畢后,也就是A線程終止后。才會(huì)喚醒B線程。
線程執(zhí)行完畢或線程終止時(shí),會(huì)調(diào)用線程自身的notifyAll()方法,會(huì)通知所有等待在該線程對(duì)象的線程。
ThreadLocal
在上述文章中,我們都是講解的多個(gè)線程之前的通信,那么在同一線程中,在某個(gè)時(shí)刻我們想獲取線程中設(shè)置的變量,我們可以通過(guò)ThreadLocal,在之前的文章中Android-Handler機(jī)制之ThreadLocal,我們介紹過(guò)ThreadLocal的使用。下面我們通過(guò)一個(gè)例子來(lái)了解ThreadLocal的使用。具體例子如下:
class ThreadLocalTest {
private static ThreadLocal<String> mThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
mThreadLocal.set("線程main”);
new Thread(new A()).start();
new Thread(new B()).start();
System.out.println(mThreadLocal.get());
}
static class A implements Runnable {
@Override
public void run() {
mThreadLocal.set("線程A”);
System.out.println(mThreadLocal.get());
}
}
static class B implements Runnable {
@Override
public void run() {
mThreadLocal.set("線程B”);
System.out.println(mThreadLocal.get());
}
}
}
輸出結(jié)果:
main
線程A
線程B
這里就不再介紹ThreadLocal的原理了,有興趣的小伙伴可以查看Android-Handler機(jī)制之ThreadLocal文章進(jìn)行理解。
最后
這里提供一個(gè)線程交替打印奇數(shù)偶數(shù)的例子,來(lái)幫助大家鞏固所學(xué)的知識(shí)點(diǎn)。有興趣的小伙伴,可以查看項(xiàng)目PrintOddEventNumber。
參考
該文章參考以下圖書,站在巨人的肩膀上??梢钥吹酶h(yuǎn)。
- 《Java并發(fā)編程的藝術(shù)》