本文主要介紹線程的定義,創(chuàng)建,使用,停止,狀態(tài)圖和常用方法。
主要用于概念掃盲和梳理。
多進(jìn)程是指操作系統(tǒng)能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)。
多線程是指在同一程序中有多個(gè)順序流在執(zhí)行。
先說并發(fā)
1. 為什么要用到并發(fā)
一直以來,硬件的發(fā)展極其迅速,也有一個(gè)很著名的"摩爾定律",可能會(huì)奇怪明明討論的是并發(fā)編程為什么會(huì)扯到了硬件的發(fā)展,這其中的關(guān)系應(yīng)該是多核CPU的發(fā)展為并發(fā)編程提供的硬件基礎(chǔ)。摩爾定律并不是一種自然法則或者是物理定律,它只是基于認(rèn)為觀測(cè)數(shù)據(jù)后,對(duì)未來的一種預(yù)測(cè)。
按照所預(yù)測(cè)的速度,我們的計(jì)算能力會(huì)按照指數(shù)級(jí)別的速度增長(zhǎng),不久以后會(huì)擁有超強(qiáng)的計(jì)算能力,正是在暢想未來的時(shí)候,2004年,Intel宣布4GHz芯片的計(jì)劃推遲到2005年,然后在2004年秋季,Intel宣布徹底取消4GHz的計(jì)劃,也就是說摩爾定律的有效性超過了半個(gè)世紀(jì)戛然而止。
但是,聰明的硬件工程師并沒有停止研發(fā)的腳步,他們?yōu)榱诉M(jìn)一步提升計(jì)算速度,而不是再追求單獨(dú)的計(jì)算單元,而是將多個(gè)計(jì)算單元整合到了一起,也就是形成了多核CPU。短短十幾年的時(shí)間,家用型CPU,比如Intel i7就可以達(dá)到4核心甚至8核心。而專業(yè)服務(wù)器則通??梢赃_(dá)到幾個(gè)獨(dú)立的CPU,每一個(gè)CPU甚至擁有多達(dá)8個(gè)以上的內(nèi)核。因此,摩爾定律似乎在CPU核心擴(kuò)展上繼續(xù)得到體驗(yàn)。因此,多核的CPU的背景下,催生了并發(fā)編程的趨勢(shì),通過并發(fā)編程的形式可以將多核CPU的計(jì)算能力發(fā)揮到極致,性能得到提升。
頂級(jí)計(jì)算機(jī)科學(xué)家Donald Ervin Knuth如此評(píng)價(jià)這種情況:在我看來,這種現(xiàn)象(并發(fā))或多或少是由于硬件設(shè)計(jì)者無計(jì)可施了導(dǎo)致的,他們將摩爾定律的責(zé)任推給了軟件開發(fā)者。
另外,在特殊的業(yè)務(wù)場(chǎng)景下先天的就適合于并發(fā)編程。比如在圖像處理領(lǐng)域,一張1024X768像素的圖片,包含達(dá)到78萬6千多個(gè)像素。即時(shí)將所有的像素遍歷一邊都需要很長(zhǎng)的時(shí)間,面對(duì)如此復(fù)雜的計(jì)算量就需要充分利用多核的計(jì)算的能力。又比如當(dāng)我們?cè)?strong>網(wǎng)上購(gòu)物時(shí),為了提升響應(yīng)速度,需要拆分,減庫(kù)存,生成訂單等等這些操作,就可以進(jìn)行拆分利用多線程的技術(shù)完成。面對(duì)復(fù)雜業(yè)務(wù)模型,并行程序會(huì)比串行程序更適應(yīng)業(yè)務(wù)需求,而并發(fā)編程更能吻合這種業(yè)務(wù)拆分 。
2. 并發(fā)編程缺點(diǎn)
- 頻繁的上下文切換
時(shí)間片是CPU分配給各個(gè)線程的時(shí)間,因?yàn)闀r(shí)間非常短,所以CPU不斷通過切換線程,讓我們覺得多個(gè)線程是同時(shí)執(zhí)行的,時(shí)間片一般是幾十毫秒。而每次切換時(shí),需要保存當(dāng)前的狀態(tài)起來,以便能夠進(jìn)行恢復(fù)先前狀態(tài),而這個(gè)切換時(shí)非常損耗性能,過于頻繁反而無法發(fā)揮出多線程編程的優(yōu)勢(shì)。
通常減少上下文切換可以采用無鎖并發(fā)編程,CAS算法,使用最少的線程和使用協(xié)程。
- 無鎖并發(fā)編程:可以參照concurrentHashMap鎖分段的思想,不同的線程處理不同段的數(shù)據(jù),這樣在多線程競(jìng)爭(zhēng)的條件下,可以減少上下文切換的時(shí)間。
- CAS算法,利用Atomic下使用CAS算法來更新數(shù)據(jù),使用了樂觀鎖,可以有效的減少一部分不必要的鎖競(jìng)爭(zhēng)帶來的上下文切換
- 使用最少線程:避免創(chuàng)建不需要的線程,比如任務(wù)很少,但是創(chuàng)建了很多的線程,這樣會(huì)造成大量的線程都處于等待狀態(tài)
- 協(xié)程:在單線程里實(shí)現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個(gè)任務(wù)間的切換
- 線程安全
3. 一些概念
3.1 同步與異步
同步和異步通常用來形容一次方法調(diào)用。同步方法調(diào)用一開始,調(diào)用者必須等待被調(diào)用的方法結(jié)束后,調(diào)用者后面的代碼才能執(zhí)行。而異步調(diào)用,指的是,調(diào)用者不用管被調(diào)用方法是否完成,都會(huì)繼續(xù)執(zhí)行后面的代碼,當(dāng)被調(diào)用的方法完成后會(huì)通知調(diào)用者。
比如,在超時(shí)購(gòu)物,如果一件物品沒了,你得等倉(cāng)庫(kù)人員跟你調(diào)貨,直到倉(cāng)庫(kù)人員跟你把貨物送過來,你才能繼續(xù)去收銀臺(tái)付款,這就類似同步調(diào)用。而異步調(diào)用了,就像網(wǎng)購(gòu),你在網(wǎng)上付款下單后,什么事就不用管了,該干嘛就干嘛去了,當(dāng)貨物到達(dá)后你收到通知去取就好。
3.2 并發(fā)與并行
并發(fā)和并行是十分容易混淆的概念。并發(fā)指的是多個(gè)任務(wù)交替進(jìn)行,而并行則是指真正意義上的“同時(shí)進(jìn)行”。實(shí)際上,如果系統(tǒng)內(nèi)只有一個(gè)CPU,而使用多線程時(shí),那么真實(shí)系統(tǒng)環(huán)境下不能并行,只能通過切換時(shí)間片的方式交替進(jìn)行,而成為并發(fā)執(zhí)行任務(wù)。
真正的并行也只能出現(xiàn)在擁有多個(gè)CPU的系統(tǒng)中。
3.3 阻塞和非阻塞
阻塞和非阻塞通常用來形容多線程間的相互影響,比如一個(gè)線程占有了臨界區(qū)資源,那么其他線程需要這個(gè)資源就必須進(jìn)行等待該資源的釋放,會(huì)導(dǎo)致等待的線程掛起,這種情況就是阻塞,而非阻塞就恰好相反,它強(qiáng)調(diào)沒有一個(gè)線程可以阻塞其他線程,所有的線程都會(huì)嘗試地往前運(yùn)行。
3.4 臨界區(qū)
臨界區(qū)用來表示一種公共資源或者說是共享數(shù)據(jù),可以被多個(gè)線程使用。但是每個(gè)線程使用時(shí),一旦臨界區(qū)資源被一個(gè)線程占有,那么其他線程必須等待。
進(jìn)程與線程
進(jìn)程--資源分配的最小單位
- 每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(進(jìn)程上下文)
- 一個(gè)進(jìn)程包含多個(gè)線程
- 進(jìn)程間的切換會(huì)有較大的開銷
線程--cpu調(diào)度的最小單位
- 同一類線程共享代碼和數(shù)據(jù)空間,
- 每個(gè)線程有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC)
- 線程切換開銷小
其他
- 多進(jìn)程是指操作系統(tǒng)能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)。
- 多線程是指在同一程序中有多個(gè)順序流在執(zhí)行。
- 線程和進(jìn)程一樣分為五個(gè)階段:創(chuàng)建、就緒、運(yùn)行、阻塞、終止。
線程創(chuàng)建、使用、停止
創(chuàng)建線程
- 在Java中要想實(shí)現(xiàn)多線程,有三種手段,一種是繼承Thread類,另外一種是實(shí)現(xiàn)Runnable接口,三是實(shí)現(xiàn)Callable 接口。
實(shí)現(xiàn) Runnable 和 Callable 接口的類只能當(dāng)做一個(gè)可以在線程中運(yùn)行的任務(wù),不是真正意義上的線程,因此最后還需要通過 Thread 來調(diào)用??梢哉f任務(wù)是通過線程驅(qū)動(dòng)從而執(zhí)行的。
1. 繼承Thread 類,覆蓋run方法(推薦使用Runable)
- run()方法是多線程程序的一個(gè)約定,所有的多線程代碼都在run方法里面。
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
- 我們復(fù)寫的是run()方法,但是執(zhí)行的卻是start()方法。
- start()方法的調(diào)用后并不是立即執(zhí)行多線程代碼,而是使得該線程變?yōu)榭蛇\(yùn)行態(tài)(Runnable),什么時(shí)候運(yùn)行是由操作系統(tǒng)決定的。
PrimeThread p = new PrimeThread(143);
p.start();
2. 實(shí)現(xiàn)Runnable接口,覆蓋run方法
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
在啟動(dòng)的多線程的時(shí)候,需要先通過Thread類的構(gòu)造方法Thread(Runnable target)構(gòu)造出對(duì)象,然后調(diào)用Thread對(duì)象的start()方法來運(yùn)行多線程代碼。
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
或
new Thread(new PrimeRun(143)).start();
實(shí)際上所有的多線程代碼都是通過運(yùn)行Thread的start()方法來運(yùn)行的。因此,不管是擴(kuò)展Thread類還是實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)多線程,最終還是通過Thread的對(duì)象的API來控制線程的,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)。
3. 實(shí)現(xiàn) Callable 接口
與 Runnable 相比,Callable 可以有返回值,返回值通過FutureTask 進(jìn)行封裝。
public class MyCallable implements Callable<Integer> {
public Integer call() {
// ...
}
public static void main(String[] args) {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
}
Thread和Runnable的區(qū)別
實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢(shì):
- 適合多個(gè)相同的程序代碼的線程去處理同一個(gè)資源
- 可以避免java中的
單繼承的限制 - 增加程序的健壯性,代碼可以被多個(gè)線程共享,代碼和數(shù)據(jù)獨(dú)立
-
線程池只能放入實(shí)現(xiàn)Runable或callable類線程,不能直接放入繼承Thread的類
使用線程
方法
| 類別 | 方法簽名 | 簡(jiǎn)介 |
|---|---|---|
| 構(gòu)造方法 | thread() | - |
| - | thread(String name) | - |
| - | thread(Runnable target) | - |
| - | thread(Runnable target,String name) | - |
| 常用方法 | void start() | 啟動(dòng)線程 |
| - | static void sleep(long millis) | 休眠 |
| - | static void sleep(long millis,int nanos) | 休眠 |
| - | void join() | 使其他線程等待當(dāng)前線程終止 |
| - | void join(long millis) | 使其他線程等待當(dāng)前線程終止 |
| - | void join(long millis,int nanos) | 使其他線程等待當(dāng)前線程終止 |
| - | static void yield( ) | 當(dāng)前運(yùn)行線程釋放處理器資源 |
| 獲取線程引用 | static Thread currendThread() | 返回當(dāng)前運(yùn)行的線程引用 |
注意事項(xiàng)
- main方法其實(shí)也是一個(gè)線程。在java中所以的線程都是同時(shí)啟動(dòng)的,至于什么時(shí)候,哪個(gè)先執(zhí)行,完全看誰先得到CPU的資源。
- 在java中,每次程序運(yùn)行至少啟動(dòng)2個(gè)線程。一個(gè)是main線程,一個(gè)是垃圾收集線程。
停止線程
錯(cuò)誤方法
- stop()方法。
它會(huì)使線程戛然而止,我們不知道它完成了什么工作,沒有完成什么工作,因此是錯(cuò)誤的。 - interrupt()方法。
不要使用interrupt()方法,因?yàn)?strong>在線程run()中調(diào)用sleep(),join(),yeild()方法時(shí),中斷狀態(tài)會(huì)被清除,isinterrupe=false
如何正確的停止線程
使用退出標(biāo)志。
volatile boolean keepRunning = true;
代碼示例
public class ActorThread extends Thread {//繼承Thread創(chuàng)建線程
// 覆蓋run------------------------------------------------------
public void run() {
System.out.println(getName() + "是一個(gè)演員!");
int count = 0;
boolean keepRunning = true;
while (keepRunning) {
System.out.println(getName() + "登臺(tái)演出:" + (++count));
//標(biāo)注法停止線程-----------------------------------------------
if (count == 15) {
keepRunning = false;
}
//測(cè)試sleep()-----------------------
if (count % 5 == 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(getName() + "的演出結(jié)束了!");
}
public static void main(String[] args) {
Thread actor = new ActorThread();
actor.setName("Mr. Thread");
actor.start();
Thread actressThread = new Thread(new Actress(), "Ms. Runnable");
actressThread.start();
}
}
class Actress implements Runnable {//實(shí)現(xiàn)Runnable接口創(chuàng)建線程
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "是一個(gè)演員!");
int count = 0;
boolean keepRunning = true;
while (keepRunning) {
System.out.println(Thread.currentThread().getName() + "登臺(tái)演出:" + (++count));
if (count == 15) {
keepRunning = false;
}
if (count % 5 == 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(Thread.currentThread().getName() + "的演出結(jié)束了!");
}
}
輸出

線程狀態(tài)轉(zhuǎn)換

- 新建狀態(tài)(New):新創(chuàng)建了一個(gè)線程對(duì)象。
- 就緒狀態(tài)(Runnable):線程對(duì)象創(chuàng)建后,其他線程調(diào)用了該對(duì)象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,變得可運(yùn)行,等待獲取CPU的使用權(quán)。
- 運(yùn)行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。
- 阻塞狀態(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)。阻塞的情況分三種:
- 等待阻塞:運(yùn)行的線程執(zhí)行wait()方法,JVM會(huì)把該線程放入等待池中。(wait會(huì)釋放持有的鎖)
- 同步阻塞:運(yùn)行的線程在獲取對(duì)象的同步鎖時(shí),若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池中。
- 其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請(qǐng)求時(shí),JVM會(huì)把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入就緒狀態(tài)。(sleep是不會(huì)釋放持有的鎖)
- 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
常用函數(shù)說明
sleep(long millis):線程暫停執(zhí)行
在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠。
sleep() 可能會(huì)拋出 InterruptedException。因?yàn)楫惓2荒芸缇€程傳播回 main() 中,因此必須在本地進(jìn)行處理。線程中拋出的其它異常也同樣需要在本地進(jìn)行處理。
join(long millis):指等待t線程一段時(shí)間(或終止)
- join是Thread類的一個(gè)方法,啟動(dòng)線程后直接調(diào)用,即join()的作用是:“等待該線程(終止)”。
- 目的
在很多情況下,主線程生成并起動(dòng)了子線程,如果子線程里要進(jìn)行大量的耗時(shí)的運(yùn)算,主線程往往將于子線程之前結(jié)束,但是如果主線程處理完其他的事務(wù)后,需要用到子線程的處理結(jié)果,也就是主線程需要等待子線程執(zhí)行完成之后再結(jié)束,這個(gè)時(shí)候就要用到j(luò)oin()方法了。
yield():暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。
- yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行機(jī)會(huì)。
- 目的
使用yield()的目的是讓相同優(yōu)先級(jí)的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實(shí)際中無法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。
wait()
本小節(jié)轉(zhuǎn)載自使用wait/notify/notifyAll實(shí)現(xiàn)線程間通信的幾點(diǎn)重要說明
用于線程間通信
在Java中,可以通過配合調(diào)用Object對(duì)象的wait()方法和notify()方法或notifyAll()方法來實(shí)現(xiàn)線程間的通信。
語(yǔ)義
在線程中調(diào)用wait()方法,將阻塞等待其他線程的通知(其他線程調(diào)用notify()方法或notifyAll()方法),在線程中調(diào)用notify()方法或notifyAll()方法,將通知其他線程從wait()方法處返回。
Object是所有類的超類,它有5個(gè)方法組成了等待/通知機(jī)制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。在Java中,所有的類都從Object繼承而來,因此,所有的類都擁有這些共有方法可供使用。而且,由于他們都被聲明為final,因此在子類中不能覆寫任何一個(gè)方法。
使用中需要注意
-
wait()--- "我去等待了"
public final void wait() throws InterruptedException,IllegalMonitorStateException
- 該方法用來將當(dāng)前線程置入休眠狀態(tài),直到接到通知或被中斷為止。在調(diào)用wait()之前,線程必須要獲得該對(duì)象的對(duì)象級(jí)別鎖,即只能在同步方法或同步塊中調(diào)用wait()方法。
- 進(jìn)入wait()方法后,當(dāng)前線程釋放鎖。在從wait()返回前,線程與其他線程競(jìng)爭(zhēng)重新獲得鎖。如果調(diào)用wait()時(shí),沒有持有適當(dāng)?shù)逆i,則拋出
IllegalMonitorStateException,它是RuntimeException的一個(gè)子類,因此,不需要try-catch結(jié)構(gòu)。
-
notify()---"大家來爭(zhēng)我"
public final native void notify() throws IllegalMonitorStateException
- 該方法也要在同步方法或同步塊中調(diào)用,即在調(diào)用前,線程也必須要獲得該對(duì)象的對(duì)象級(jí)別鎖,的如果調(diào)用notify()時(shí)沒有持有適當(dāng)?shù)逆i,也會(huì)拋出
IllegalMonitorStateException - 該方法用來通知那些可能等待該對(duì)象的對(duì)象鎖的其他線程。如果有多個(gè)線程等待,則線程規(guī)劃器任意挑選出其中一個(gè)wait()狀態(tài)的線程來發(fā)出通知,并使它等待獲取該對(duì)象的對(duì)象鎖(notify后,當(dāng)前線程不會(huì)馬上釋放該對(duì)象鎖,wait所在的線程并不能馬上獲取該對(duì)象鎖,要等到程序退出synchronized代碼塊后,當(dāng)前線程才會(huì)釋放鎖,wait所在的線程也才可以獲取該對(duì)象鎖),但不驚動(dòng)其他同樣在等待被該對(duì)象notify的線程們。當(dāng)?shù)谝粋€(gè)獲得了該對(duì)象鎖的wait線程運(yùn)行完畢以后,它會(huì)釋放掉該對(duì)象鎖,此時(shí)如果該對(duì)象沒有再次使用notify語(yǔ)句,則即便該對(duì)象已經(jīng)空閑,其他wait狀態(tài)等待的線程由于沒有得到該對(duì)象的通知,會(huì)繼續(xù)阻塞在wait狀態(tài),直到這個(gè)對(duì)象發(fā)出一個(gè)notify或notifyAll。這里需要注意:它們等待的是被notify或notifyAll,而不是鎖。這與下面的notifyAll()方法執(zhí)行后的情況不同。
notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
該方法與notify()方法的工作方式相同,重要的一點(diǎn)差異是:
notifyAll使所有原來在該對(duì)象上wait的線程統(tǒng)統(tǒng)退出wait的狀態(tài)(即全部被喚醒,不再等待notify或notifyAll,但由于此時(shí)還沒有獲取到該對(duì)象鎖,因此還不能繼續(xù)往下執(zhí)行),變成等待獲取該對(duì)象上的鎖,一旦該對(duì)象鎖被釋放(notifyAll線程退出調(diào)用了notifyAll的synchronized代碼塊的時(shí)候),他們就會(huì)去競(jìng)爭(zhēng)。如果其中一個(gè)線程獲得了該對(duì)象鎖,它就會(huì)繼續(xù)往下執(zhí)行,在它退出synchronized代碼塊,釋放鎖后,其他的已經(jīng)被喚醒的線程將會(huì)繼續(xù)競(jìng)爭(zhēng)獲取該鎖,一直進(jìn)行下去,直到所有被喚醒的線程都執(zhí)行完畢。
- wait(long)和wait(long,int)
顯然,這兩個(gè)方法是設(shè)置等待超時(shí)時(shí)間的,后者在超值時(shí)間上加上ns,精度也難以達(dá)到,因此,該方法很少使用。對(duì)于前者,如果在等待線程接到通知或被中斷之前,已經(jīng)超過了指定的毫秒數(shù),則它通過競(jìng)爭(zhēng)重新獲得鎖,并從wait(long)返回。另外,需要知道,如果設(shè)置了超時(shí)時(shí)間,當(dāng)wait()返回時(shí),我們不能確定它是因?yàn)榻拥搅送ㄖ€是因?yàn)槌瑫r(shí)而返回的,因?yàn)閣ait()方法不會(huì)返回任何相關(guān)的信息。但一般可以通過設(shè)置標(biāo)志位來判斷,在notify之前改變標(biāo)志位的值,在wait()方法后讀取該標(biāo)志位的值來判斷,當(dāng)然為了保證notify不被遺漏,我們還需要另外一個(gè)標(biāo)志位來循環(huán)判斷是否調(diào)用wait()方法。 - 深入理解:
如果線程調(diào)用了對(duì)象的wait()方法,那么線程便會(huì)處于該對(duì)象的
等待池中,等待池中的線程不會(huì)去競(jìng)爭(zhēng)該對(duì)象的鎖。
當(dāng)有線程調(diào)用了對(duì)象的notifyAll()方法(喚醒所有wait線程)或notify()方法(只隨機(jī)喚醒一個(gè)wait線程),被喚醒的的線程便會(huì)進(jìn)入該對(duì)象的鎖池中,鎖池中的線程會(huì)去競(jìng)爭(zhēng)該對(duì)象鎖。
優(yōu)先級(jí)高的線程競(jìng)爭(zhēng)到對(duì)象鎖的概率大,假若某線程沒有競(jìng)爭(zhēng)到該對(duì)象鎖,它還會(huì)留在鎖池中,唯有線程再次調(diào)用wait()方法,它才會(huì)重新回到等待池中。而競(jìng)爭(zhēng)到對(duì)象鎖的線程則繼續(xù)往下執(zhí)行,直到執(zhí)行完了synchronized代碼塊,它會(huì)釋放掉該對(duì)象鎖,這時(shí)鎖池中的線程會(huì)繼續(xù)競(jìng)爭(zhēng)該對(duì)象鎖。
wait和sleep區(qū)別
共同點(diǎn):
- 他們都是在多線程的環(huán)境下,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù),并返回
- wait()和sleep()都可以通過interrupt()方法 打斷線程的暫停狀態(tài) ,從而使線程立刻拋出InterruptedException。 (不建議使用該方法)
- 如果線程A希望立即結(jié)束線程B,則可以對(duì)線程B對(duì)應(yīng)的Thread實(shí)例調(diào)用interrupt方法。如果此刻線程B正在wait/sleep /join,則線程B會(huì)立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結(jié)束線程。
- 需要注意的是,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的。對(duì)某一線程調(diào)用 interrupt()時(shí),如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會(huì)拋出InterruptedException。但是,一旦該線程進(jìn)入到 wait()/sleep()/join()后,就會(huì)立刻拋出InterruptedException 。
不同點(diǎn):
- 所屬的類
Thread類的方法:sleep(),yield()等
Object的方法:wait()和notify()等 - 是否釋放鎖
每個(gè)對(duì)象都有一個(gè)鎖來控制同步訪問。Synchronized關(guān)鍵字可以和對(duì)象的鎖交互,來實(shí)現(xiàn)線程的同步。
sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。 - 使用限制
wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用 。
sleep()和yield()的區(qū)別
- sleep 方法使當(dāng)前運(yùn)行中的線程睡眼一段時(shí)間,進(jìn)入不可運(yùn)行狀態(tài),這段時(shí)間的長(zhǎng)短是由程序設(shè)定的,yield 方法使當(dāng)前線程讓出 CPU 占有權(quán),但讓出的時(shí)間是不可設(shè)定的。實(shí)際上,yield()方法對(duì)應(yīng)了如下操作:先檢測(cè)當(dāng)前是否有相同優(yōu)先級(jí)的線程處于同可運(yùn)行狀態(tài),如有,則把 CPU 的占有權(quán)交給此線程,否則,繼續(xù)運(yùn)行原來的線程。所以yield()方法稱為“退讓”,它把運(yùn)行機(jī)會(huì)讓給了同等優(yōu)先級(jí)的其他線程
- 另外,sleep 方法允許較低優(yōu)先級(jí)的線程獲得運(yùn)行機(jī)會(huì),但 yield() 方法執(zhí)行時(shí),當(dāng)前線程仍處在可運(yùn)行狀態(tài),所以,不可能讓出較低優(yōu)先級(jí)的線程些時(shí)獲得 CPU 占有權(quán)。在一個(gè)運(yùn)行系統(tǒng)中,如果較高優(yōu)先級(jí)的線程沒有調(diào)用 sleep 方法,又沒有受到 I\O 阻塞,那么,較低優(yōu)先級(jí)線程只能等待所有較高優(yōu)先級(jí)的線程運(yùn)行結(jié)束,才有機(jī)會(huì)運(yùn)行。
三線程打印ABC
要求
java實(shí)現(xiàn)三個(gè)線程A B C:A線程打印10次A,B線程打印10次B,C線程打印10次C,要求線程同時(shí)運(yùn)行,交替打印10次ABC。
方法一.使用synchronized() + wait(),nitify()
- 題意
問題為三線程間的同步喚醒操作,主要的目的就是ThreadA->ThreadB->ThreadC→ThreadA……循環(huán)執(zhí)行三個(gè)線程。為了控制線程執(zhí)行的順序,那么就必須要確定喚醒、等待的順序,所以每一個(gè)線程必須同時(shí)持有兩個(gè)對(duì)象鎖,才能繼續(xù)執(zhí)行。一個(gè)對(duì)象鎖是prev,就是前一個(gè)線程所持有的對(duì)象鎖。還有一個(gè)就是自身對(duì)象鎖。 - 思路
主要的思想就是,為了控制執(zhí)行的順序,必須要先持有prev鎖,也就是前一個(gè)線程要釋放自身對(duì)象鎖,再去申請(qǐng)自身對(duì)象鎖,兩者兼?zhèn)鋾r(shí)打印字母,之后首先調(diào)用self.notifyAll()釋放自身對(duì)象鎖,喚醒下一個(gè)等待線程,再調(diào)用prev.wait()釋放prev對(duì)象鎖,終止當(dāng)前線程,等待循環(huán)結(jié)束后再次被喚醒。
程序運(yùn)行的主要過程就是
A線程最先運(yùn)行,持有C,A對(duì)象鎖,后釋放A,C鎖,喚醒B;
線程B等待A鎖,再申請(qǐng)B鎖,后打印B,再釋放B,A鎖,喚醒C;
線程C等待B鎖,再申請(qǐng)C鎖,后打印C,再釋放C,B鎖,喚醒A……
- 注意
為了避免JVM啟動(dòng)ThreadA、ThreadB、ThreadC三個(gè)線程順序的不確定性。需要讓A,B,C三個(gè)線程以確定的順序啟動(dòng),中間加一段sleep()確保前一個(gè)線程已啟動(dòng)。
代碼實(shí)現(xiàn)
public class ABC {
public static class ThreadPrinter implements Runnable {
private String name;
private Object prev;//前一個(gè)對(duì)象的鎖
private Object self;//自己的鎖
private ThreadPrinter(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {// 多線程并發(fā),不能用if,必須用循環(huán)測(cè)試等待條件,避免虛假喚醒
synchronized (prev) { // 先獲取 prev 鎖
synchronized (self) {// 再獲取 self 鎖
System.out.print(name);
count--;
self.notifyAll();// 先釋放 self,喚醒其他線程競(jìng)爭(zhēng)self鎖
}
try {
prev.wait(); // 再釋放 prev,休眠等待喚醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
ThreadPrinter pa = new ThreadPrinter("A", c, a);
ThreadPrinter pb = new ThreadPrinter("B", a, b);
ThreadPrinter pc = new ThreadPrinter("C", b, c);
new Thread(pa).start();
Thread.sleep(10);
new Thread(pb).start();
Thread.sleep(10);
new Thread(pc).start();
Thread.sleep(10);
}
}
輸出

方法二. 使用Lock
代碼實(shí)現(xiàn)
import java.util.concurrent.locks.*;
public class ABC_Lock {
private static Lock lock = new ReentrantLock();//通過Lock鎖來保證線程的訪問的互斥
private static int state = 0;//控制ABC的打印語(yǔ)句是否執(zhí)行,獲取Lock之后的判斷
static class ThreadA extends Thread {
@Override
public void run() {
int count = 10;
while(count > 0) {
try {
lock.lock();
while (state % 3 == 0) {//多線程并發(fā),不能用if,必須用循環(huán)測(cè)試等待條件,避免虛假喚醒
System.out.print("A");
state++;
count--;
}
} finally {
lock.unlock();// lock()和unlock()操作結(jié)合try/catch使用
}
}
}
}
static class ThreadB extends Thread {
@Override
public void run() {
int count = 10;
while(count > 0) {
try {
lock.lock();
while (state % 3 == 1) {//多線程并發(fā),不能用if,必須用循環(huán)測(cè)試等待條件,避免虛假喚醒
System.out.print("B");
state++;
count--;
}
} finally {
lock.unlock();// lock()和unlock()操作結(jié)合try/catch使用
}
}
}
}
static class ThreadC extends Thread {
@Override
public void run() {
int count = 10;
while(count > 0){
try {
lock.lock();
while (state % 3 == 2) {//多線程并發(fā),不能用if,必須用循環(huán)測(cè)試等待條件,避免虛假喚醒
System.out.print("C");
state++;
count--;
}
} finally {
lock.unlock();// lock()和unlock()操作結(jié)合try/catch使用
}
}
}
}
public static void main(String[] args) {
new ThreadA().start();
new ThreadB().start();
new ThreadC().start();
}
}
輸出

參考文章
并發(fā)編程的優(yōu)缺點(diǎn)
java多線程入門學(xué)習(xí)(一)
Java多線程學(xué)習(xí)(吐血超詳細(xì)總結(jié))
oracle官方文檔