核心內(nèi)容:在實際開發(fā)中,若程序需要同時處理多個任務(wù)時,我們該如何實現(xiàn)?此時多線程就可幫助我們實現(xiàn)。使用多線程可以提高CPU的利用率及程序的處理效率。本篇將會學(xué)習(xí)多線程相關(guān)概念、創(chuàng)建和使用、線程安全問題及線程狀態(tài)的了解。
第一章:多線程基礎(chǔ)
本章主要了解和多線程相關(guān)的一些概念。
想要設(shè)計一個程序,邊打游戲邊聽歌,怎么設(shè)計?
要解決上述問題,得使用多進(jìn)程或者多線程來解決.
1.1-并發(fā)和并行(了解)
并發(fā)
并發(fā)簡而言之就是:指兩個或多個事件在同一個時間段內(nèi)發(fā)生 (交替執(zhí)行)。
在操作系統(tǒng)中,安裝了多個程序,并發(fā)指的是在一段時間內(nèi)宏觀上有多個程序同時運行,這在單 CPU 系統(tǒng)中,每 一時刻只能有一道程序執(zhí)行,即微觀上這些程序是分時的交替運行,只不過是給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的。

并行
簡而言之,并行:是指兩個或多個事件在同一時刻發(fā)生(同時發(fā)生)。
而在多個 CPU 系統(tǒng)中,則這些可以并發(fā)執(zhí)行的程序便可以分配到多個處理器上(CPU),實現(xiàn)多任務(wù)并行執(zhí)行,即利用每個處理器來處理一個可以并發(fā)執(zhí)行的程序,這樣多個程序便可以同時執(zhí)行。目前電腦市場上說的多核 CPU,便是多核處理器,核越多,并行處理的程序越多,能大大的提高電腦運行的效率。

1.2-進(jìn)程與線程 (了解)
進(jìn)程
是指一個內(nèi)存中運行的應(yīng)用程序,每個進(jìn)程都有一個獨立的內(nèi)存空間,一個應(yīng)用程序可以同時運行多個進(jìn)程;進(jìn)程也是程序的一次執(zhí)行過程,是系統(tǒng)運行程序的基本單位;系統(tǒng)運行一個程序即是一個進(jìn)程從創(chuàng)建、運行到消亡的過程。

線程
線程是進(jìn)程中的一個執(zhí)行單元,負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行,一個進(jìn)程中至少有一個線程。一個進(jìn)程
中是可以有多個線程的,這個應(yīng)用程序也可以稱之為多線程程序。

注意
一個程序運行后至少有一個進(jìn)程,一個進(jìn)程中可以包含多個線程。
由于創(chuàng)建一個線程的開銷比創(chuàng)建一個進(jìn)程的開銷小的多,那么我們在開發(fā)多任務(wù)運行的時候,通??紤]創(chuàng)建多線程,而不是創(chuàng)建多進(jìn)程。
多線程可以提高cpu利用率
大部分操作系統(tǒng)都支持多進(jìn)程并發(fā)運行,現(xiàn)在的操作系統(tǒng)幾乎都支持同時運行多個程序。在同時運行的程序,”感覺這些軟件好像在同一時刻運行著“。
實際上,CPU(中央處理器)使用搶占式調(diào)度模式在多個線程間進(jìn)行著高速的切換。對于CPU的一個核而言,某個時刻,只能執(zhí)行一個線程,而 CPU的在多個線程間切換速度相對我們的感覺要快,看上去就是在同一時刻運行。 其實,多線程程序并不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。
1.3-線程調(diào)度(了解)
分時調(diào)度
所有線程輪流使用 CPU 的使用權(quán),平均分配每個線程占用 CPU 的時間。
搶占式調(diào)度
優(yōu)先讓優(yōu)先級高的線程使用 CPU,如果線程的優(yōu)先級相同,那么會隨機選擇一個(線程隨機性),Java使用的為搶占式調(diào)度。

第二章:Java中創(chuàng)建和使用多線程
2.1-繼承Thread類方式創(chuàng)建線程(重要)
Java使用 java.lang.Thread 類代表線程,所有的線程對象都必須是Thread類或其子類的實例。每個線程的作用是 完成一定的任務(wù),實際上就是執(zhí)行一段程序流即一段順序執(zhí)行的代碼。Java使用線程執(zhí)行體來代表這段程流。
Java中通過繼承Thread類來創(chuàng)建并啟動多線程的步驟如下:
- 定義Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務(wù),因此把 run()方法稱為線程執(zhí)行體。
- 創(chuàng)建Thread子類的實例,即創(chuàng)建了線程對象 。
- 調(diào)用線程對象的start()方法來啟動該線程 。
Thread類構(gòu)造方法
- public Thread() :分配一個新的線程對象。
- public Thread(String name) :分配一個指定名字的新的線程對象。
示例代碼
/*測試類中的代碼*/
public class DemoThread {
public static void main(String[] args) {
MyThread mt = new MyThread("線程1");
mt.start(); // 啟動線程1的任務(wù)
MyThread mt2 = new MyThread("線程2");
mt2.start(); // 啟動線程2的任務(wù)
}
}
/*定義的線程類代碼*/
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(getName() + "線程執(zhí)行" + i);
}
}
}
2.2-多線程原理(了解)
多個線程之間的程序不會影響彼此(比如一個線程崩潰了并不會影響另一個線程)。
在Java中,main方法是程序執(zhí)行的入口,也是Java程序的主線程。當(dāng)在程序中開辟新的線程時,執(zhí)行過程是這樣的。
執(zhí)行過程
首先main方法作為主程序先壓棧執(zhí)行。
在主程序的執(zhí)行過程中,若創(chuàng)建了新的線程,則內(nèi)存中會另開辟一個新的棧來執(zhí)行新的線程。
每一個新的線程都會有一個新的棧來存放新的線程任務(wù)。
棧與棧之間的任務(wù)不會互相影響。
CPU會隨機切換執(zhí)行不同棧中的任務(wù)。
圖解執(zhí)行過程(以上述代碼為例)

2.3-Thread類常用方法(重要)
常用方法
- public String getName() :獲取當(dāng)前線程名稱。
- public void start() :導(dǎo)致此線程開始執(zhí)行; Java虛擬機調(diào)用此線程的run方法。
- public void run() :此線程要執(zhí)行的任務(wù)在此處定義代碼。
- public static void sleep(long millis) :使當(dāng)前正在執(zhí)行的線程以指定的毫秒數(shù)暫停(暫時停止執(zhí)行)。
- public static Thread currentThread() :返回對當(dāng)前正在執(zhí)行的線程對象的引用。
示例代碼
//【代碼測試類】
public class Main01 {
public static void main(String[] args) {
MyThread mt = new MyThread("線程1");
mt.start();
// 打印線程名稱
System.out.println(mt.getName());
System.out.println("當(dāng)前線程是" + Thread.currentThread().getName());
// 每間隔一秒鐘打印一個數(shù)字
for (int i = 0; i < 60; i++) {
System.out.println(i);
try {
// sleep拋出了異常,需要處理異常
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//【MyThread類】
public class MyThread extends Thread{
public MyThread(){
super();
}
// 構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)傳入線程名稱
public MyThread(String name) {
super(name);
}
@Override
public void run() {
// 打印線程名稱
System.out.println(this.getName());
System.out.println("當(dāng)前線程是" + Thread.currentThread().getName());
}
}
run方法和start方法
run()方法,是線程執(zhí)行的任務(wù)方法,每個線程都會調(diào)用run()方法執(zhí)行,我們將線程要執(zhí)行的任務(wù)代碼都寫在run()方法中就可以被線程調(diào)用執(zhí)行。
start()方法,開啟線程,線程調(diào)用run()方法。start()方法源代碼中會調(diào)用本地方法start0()來啟動線程:private native void start0(),本地方法都是和操作系統(tǒng)交互的,因此可以看出每次開啟一個線程的線程都會和操作系統(tǒng)進(jìn)行交互。
注意:一個線程只能被啟動一次!
關(guān)于線程的名字
線程是有默認(rèn)名字的,如果我們不設(shè)置線程的名字,JVM會賦予線程默認(rèn)名字Thread-0,Thread-1。
2.4-實現(xiàn)Runnable接口方式創(chuàng)建線程(重要)
翻閱API后得知創(chuàng)建線程的方式總共有兩種,一種是繼承Thread類方式,一種是實現(xiàn)Runnable接口方式 。
Runnable使用步驟
- 定義Runnable接口的實現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。
- 創(chuàng)建Runnable實現(xiàn)類的實例,并以此實例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。
- 調(diào)用線程對象的start()方法來啟動線程。
Thread類構(gòu)造函數(shù)
- public Thread(Runnable target) :分配一個帶有指定目標(biāo)新的線程對象。
- public Thread(Runnable target,String name) :分配一個帶有指定目標(biāo)新的線程對象并指定名字。
示例代碼
// 測試類
public class Main01 {
public static void main(String[] args) {
// 創(chuàng)建Runnable對象
RunnableImpl ra = new RunnableImpl();
// 創(chuàng)建線程對象并傳入Runnable對象
Thread th = new Thread(ra);
// 啟動并執(zhí)行線程任務(wù)
th.start();
}
}
// 【Runnable實現(xiàn)類】
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println("線程任務(wù)1");
}
}
總結(jié)
- 通過實現(xiàn)Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個執(zhí)行目標(biāo)。所有的多線程代碼都在run方法里面。Thread類實際上也是實現(xiàn)了Runnable接口的類。
- 在啟動的多線程的時候,需要先通過Thread類的構(gòu)造方法Thread(Runnable target) 構(gòu)造出對象,然后調(diào)用Thread對象的start()方法來運行多線程代碼。
- 實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是繼承Thread類還是實現(xiàn)Runnable接口來實現(xiàn)多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)。
- Runnable對象僅僅作為Thread對象的target,Runnable實現(xiàn)類里包含的run()方法僅作為線程執(zhí)行體。而實際的線程對象依然是Thread實例,只是該Thread線程負(fù)責(zé)執(zhí)行其target的run()方法。
2.5-Runnable和Thread的關(guān)系(了解)
創(chuàng)建線程方式2好像比創(chuàng)建線程方式1操作要麻煩一些,為何要多此一舉呢?
因為如果一個類繼承Thread,則不適合資源共享。但是如果實現(xiàn)了Runable接口的話,則很容易的實現(xiàn)資源共享。
實現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:
- 適合多個相同的程序代碼的線程去共享同一個資源。
- 可以避免java中的單繼承的局限性。
- 增加程序的健壯性,實現(xiàn)解耦操作,代碼可以被多個線程共享,代碼和線程獨立。
- 線程池只能放入實現(xiàn)Runable或Callable類線程,不能直接放入繼承Thread的類。(后面篇幅介紹)
擴(kuò)展了解
在java中,每次程序運行至少啟動2個線程。一個是main線程,一個是垃圾收集線程。因為每當(dāng)使用java命令執(zhí)行一個類的時候,實際上都會啟動一個JVM,每一個JVM其實在就是在操作系統(tǒng)中啟動了一個進(jìn)程。
2.6-匿名內(nèi)部類方式實現(xiàn)線程創(chuàng)建(重要)
使用線程的內(nèi)匿名內(nèi)部類方式,可以方便的實現(xiàn)每個線程執(zhí)行不同的線程任務(wù)操作。
簡而言之,使用匿名內(nèi)部類可以簡化代碼。
// 匿名內(nèi)部類創(chuàng)建線程方式1
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();
// 匿名內(nèi)部類創(chuàng)建線程方式2
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
第三章:線程安全問題
3.1-線程安全概述(理解)
多個線程執(zhí)行同一個任務(wù)并操作同一個數(shù)據(jù)時,就會造成數(shù)據(jù)的安全問題。我們通過以下案例來看線程安全問題。
案例需求
電影院要賣票,我們模擬電影院的賣票過程。假設(shè)要播放的電影是 “皮卡丘大戰(zhàn)葫蘆娃”,本次電影的座位共100個 (本場電影只能賣100張票)。
我們來模擬電影院的售票窗口,實現(xiàn)多個窗口同時賣 “皮卡丘大戰(zhàn)葫蘆娃”這場電影票(多個窗口一起賣這100張票) 需要窗口,采用線程對象來模擬;需要票,Runnable接口子類來模擬 。
案例代碼
//【操作票的任務(wù)代碼類】
public class RunnableImpl implements Runnable {
// 線程任務(wù)要操作的數(shù)據(jù)(100張電影票)
private int ticket = 100;
// 線程要執(zhí)行的任務(wù)
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket+"張票");
ticket--;
}else {
break;
}
}
}
}
//【測試類】
public class Main01 {
public static void main(String[] args) {
// 創(chuàng)建線程任務(wù)
RunnableImpl ra = new RunnableImpl();
// 創(chuàng)建第一個線程執(zhí)行線程任務(wù)
new Thread(ra).start();
// 創(chuàng)建第二線程執(zhí)行線程任務(wù)
new Thread(ra).start();
// 創(chuàng)建第三個線程執(zhí)行線程任務(wù)
new Thread(ra).start();
}
}
執(zhí)行結(jié)果及問題

問題原因
搶奪cpu執(zhí)行權(quán)和線程執(zhí)行時間是不確定的,比如線程0搶到了cpu執(zhí)行權(quán)并執(zhí)行到了打印代碼處,此時cpu又被線程1搶奪,其他線程處于等待線程1頁執(zhí)行到了打印代碼處,沒等ticket--,兩個線程都打印了售票信息。
這種問題,幾個窗口(線程)票數(shù)不同步了,這種問題稱為線程不安全。
線程安全問題都是由全局變量及靜態(tài)變量引起的。若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫 操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步, 否則的話就可能影響線程安全。
3.2-線程安全解決方案(重要)
上述我們知道,線程安全問題是因為線程在操作數(shù)據(jù)時不同步造成的,所以只要能夠?qū)崿F(xiàn)操作數(shù)據(jù)同步,就可以解決線程安全問題。
同步指的就是,當(dāng)一個線程執(zhí)行指定同步的代碼任務(wù)時,其他線程必須等該線程操作完畢后再執(zhí)行。
根據(jù)案例描述:窗口1線程進(jìn)入操作的時候,窗口2和窗口3線程只能在外等著,窗口1操作結(jié)束,窗口1和窗口2和窗口3才有機會進(jìn)入代碼 去執(zhí)行。也就是說在某個線程修改共享資源的時候,其他線程不能修改該資源,等待修改完畢同步之后,才能去搶奪CPU 資源,完成對應(yīng)的操作,保證了數(shù)據(jù)的同步性,解決了線程不安全的現(xiàn)象。
為了保證每個線程都能正常執(zhí)行原子操作,Java引入了線程同步機制(synchronize)。
那么怎么去使用呢?有三種方式完成同步操作:
- 同步代碼塊
- 同步方法
- 同步鎖
3.3-同步代碼塊(重要)
概述
同步代碼塊: synchronized關(guān)鍵字可以用于方法中的某個區(qū)塊中,表示只對這個區(qū)塊的資源實行互斥訪問。
格式
synchronized(同步鎖){ 需要同步操作的代碼 }
- 同步鎖:對象的同步鎖只是一個概念,可以想象為在對象上標(biāo)記了一個鎖。
- 鎖對象可以是任意類型。
- 多個線程對象 要使用同一把鎖。
- 注意:在任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖就進(jìn)入代碼塊,其他的線程只能在外等著 。
示例代碼
//【測試類】
public class Main01 {
public static void main(String[] args) {
// 創(chuàng)建線程任務(wù)
RunnableImpl ra = new RunnableImpl();
// 創(chuàng)建第一個線程執(zhí)行線程任務(wù)
new Thread(ra).start();
// 創(chuàng)建第二線程執(zhí)行線程任務(wù)
new Thread(ra).start();
// 創(chuàng)建第三個線程執(zhí)行線程任務(wù)
new Thread(ra).start();
}
}
//【線程任務(wù)類】
public class RunnableImpl implements Runnable {
// 線程任務(wù)要操作的數(shù)據(jù)
private int ticket = 100;
// 定義線程鎖對象(任意對象)
Object obj = new Object();
// 線程任務(wù)
@Override
public void run() {
while (true){
synchronized (obj){
if(ticket>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"在售賣第" + ticket + "張票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
3.4-同步方法(重要)
概述
同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時候,其他線程只能在方法外等著
格式
public synchronized void method(){ // 可能會產(chǎn)生線程安全問題的代碼 }
同步鎖是誰?
- 對于非static方法,同步鎖就是this
- 對于static方法,我們使用當(dāng)前方法所在類的字節(jié)碼對象(類名.class)。
示例代碼
//【測試類】
public class Main01 {
public static void main(String[] args) {
// 創(chuàng)建線程任務(wù)
RunnableImpl ra = new RunnableImpl();
// 創(chuàng)建第一個線程執(zhí)行線程任務(wù)
new Thread(ra).start();
// 創(chuàng)建第二線程執(zhí)行線程任務(wù)
new Thread(ra).start();
// 創(chuàng)建第三個線程執(zhí)行線程任務(wù)
new Thread(ra).start();
}
}
//【線程任務(wù)類】
public class RunnableImpl implements Runnable {
// 線程任務(wù)要操作的數(shù)據(jù)
private int ticket = 100;
@Override
public void run() {
while (true) {
int flag = func();
if(flag==0) {
break;
}
}
}
public synchronized int func() {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在售賣第" + ticket + "張票");
ticket--;
return 1;
}else {
return 0;
}
}
}
3.5-同步鎖(重要)
概述
Lock:java.util.concurrent.locks.Lock 機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作, 同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現(xiàn)面向?qū)ο蟆?/p>
格式
Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了
-
public void lock():加同步鎖。 -
public void unlock():釋放同步鎖。
示例代碼
//【測試類】
public class Main01 {
public static void main(String[] args) {
// 創(chuàng)建線程任務(wù)
RunnableImpl ra = new RunnableImpl();
// 創(chuàng)建第一個線程執(zhí)行線程任務(wù)
new Thread(ra).start();
// 創(chuàng)建第二線程執(zhí)行線程任務(wù)
new Thread(ra).start();
// 創(chuàng)建第三個線程執(zhí)行線程任務(wù)
new Thread(ra).start();
}
}
//【線程任務(wù)類】
public class RunnableImpl implements Runnable {
// 線程任務(wù)要操作的數(shù)據(jù)
private int ticket = 100;
// 創(chuàng)建鎖對象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 開啟同步鎖
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "在售賣第" + ticket + "張票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 釋放同步鎖
lock.unlock();
}
}else {
break;
}
}
}
}
第四章:線程狀態(tài)

4.1-線程狀態(tài)介紹(了解)
當(dāng)線程被創(chuàng)建并啟動以后,它既不是一啟動就進(jìn)入了執(zhí)行狀態(tài),也不是一直處于執(zhí)行狀態(tài)。在線程的生命周期中, 有幾種狀態(tài)呢?在API中 java.lang.Thread.State 這個枚舉中給出了六種線程狀態(tài):

我們不需要去研究這幾種狀態(tài)的實現(xiàn)原理,我們只需知道在做線程操作中存在這樣的狀態(tài)。那我們怎么去理解這幾 個狀態(tài)呢,新建與被終止還是很容易理解的,我們就研究一下線程從Runnable(可運行)狀態(tài)與非運行狀態(tài)之間 的轉(zhuǎn)換問題。
4.2-TimedWaiting計時等待(了解)
概述
Timed Waiting在API中的描述為:一個正在限時等待另一個線程執(zhí)行一個(喚醒)動作的線程處于這一狀態(tài)。
單獨 的去理解這句話,真是玄之又玄,其實我們在之前的操作中已經(jīng)接觸過這個狀態(tài)了,在哪里呢? 在我們寫賣票的案例中,為了減少線程執(zhí)行太快,現(xiàn)象不明顯等問題,我們在run方法中添加了sleep語句,這樣就 強制當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),以“減慢線程”。
其實當(dāng)我們調(diào)用了sleep方法之后,當(dāng)前執(zhí)行的線程就進(jìn)入到“休眠狀態(tài)”,其實就是所謂的Timed Waiting(計時等 待),那么我們通過一個案例加深對該狀態(tài)的一個理解。
示例
需求:實現(xiàn)一個計數(shù)器,計數(shù)到100,在每個數(shù)字之間暫停1秒,每隔10個數(shù)字輸出一個字符串 。
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
if ((i) % 10 == 0) {
System.out.println("‐‐‐‐‐‐‐" + i);
}
System.out.print(i);
try {
Thread.sleep(1000);
System.out.print(" 線程睡眠1秒!\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MyThread().start();
}
}
通過案例可以發(fā)現(xiàn),sleep方法的使用還是很簡單的。我們需要記住下面幾點:
- 進(jìn)入 TIMED_WAITING 狀態(tài)的一種常見情形是調(diào)用的 sleep 方法,單獨的線程也可以調(diào)用,不一定非要有協(xié) 作關(guān)系。
- 為了讓其他線程有機會執(zhí)行,可以將Thread.sleep()的調(diào)用放線程run()之內(nèi)。這樣才能保證該線程執(zhí)行過程 中會睡眠 。
- sleep與鎖無關(guān),線程睡眠到期自動蘇醒,并返回到Runnable(可運行)狀態(tài) 。
注意:sleep()中指定的時間是線程不會運行的最短時間。因此,sleep()方法不能保證該線程睡眠到期后就 開始立刻執(zhí)行。
圖解

4.3-Blocked鎖阻塞(了解)
概述
Blocked狀態(tài)在API中的介紹為:一個正在阻塞等待一個監(jiān)視器鎖(鎖對象)的線程處于這一狀態(tài) 。
我們已經(jīng)學(xué)完同步機制,那么這個狀態(tài)是非常好理解的了。比如,線程A與線程B代碼中使用同一鎖,如果線程A獲 取到鎖,線程A進(jìn)入到Runnable狀態(tài),那么線程B就進(jìn)入到Blocked鎖阻塞狀態(tài)。
這是由Runnable狀態(tài)進(jìn)入Blocked狀態(tài)。除此Waiting以及Time Waiting狀態(tài)也會在某種情況下進(jìn)入阻塞狀態(tài)。
圖解

4.4-Waiting 無限等待(了解)
概述
Wating狀態(tài)在API中介紹為:一個正在無限期等待另一個線程執(zhí)行一個特別的(喚醒)動作的線程處于這一狀態(tài)。
示例
我們通過一段代碼來 學(xué)習(xí)一下:需求如下,消費者吃包子。過程如下:
- 消費者問:包子好了嗎? 處于等待...
- 3秒鐘后....
- 老板答:包子好了
- 消費者:可以吃包子了
示例代碼:
public class Test04 {
// 鎖對象
public static Object obj = new Object();
public static void main(String[] args) {
// 【消費者線程】
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "-顧客1:老板包子好了嗎?");
try {
// 等待,釋放鎖,處于阻塞狀態(tài)
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 喚醒之后要執(zhí)行的代碼
System.out.println(Thread.currentThread().getName() + "顧客1:可以吃包子了。");
System.out.println("--------------------------------------");
}
}
}
}).start();
// 【生產(chǎn)者線程】
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("等待3秒后...");
System.out.println(Thread.currentThread().getName() + "老板說:包子好了!");
// 喚醒,喚醒其他被阻塞的線程
obj.notify();
}
}
}
}).start();
}
}
分析
通過上述案例我們會發(fā)現(xiàn),一個調(diào)用了某個對象的 Object.wait 方法的線程會等待另一個線程調(diào)用此對象的 Object.notify()方法 或 Object.notifyAll()方法 。
其實waiting狀態(tài)并不是一個線程的操作,它體現(xiàn)的是多個線程間的通信,可以理解為多個線程之間的協(xié)作關(guān)系, 多個線程會爭取鎖,同時相互之間又存在協(xié)作關(guān)系。就好比在公司里你和你的同事們,你們可能存在晉升時的競 爭,但更多時候你們更多是一起合作以完成某些任務(wù)。
當(dāng)多個線程協(xié)作時,比如A,B線程,如果A線程在Runnable(可運行)狀態(tài)中調(diào)用了wait()方法那么A線程就進(jìn)入 了Waiting(無限等待)狀態(tài),同時失去了同步鎖。假如這個時候B線程獲取到了同步鎖,在運行狀態(tài)中調(diào)用了 notify()方法,那么就會將無限等待的A線程喚醒。注意是喚醒,如果獲取到鎖對象,那么A線程喚醒后就進(jìn)入 Runnable(可運行)狀態(tài);如果沒有獲取鎖對象,那么就進(jìn)入到Blocked(鎖阻塞狀態(tài))。
圖解
