多線程編程

多線程編程

進(jìn)程

一般可以在同一時(shí)間內(nèi)執(zhí)行多個(gè)程序的操作系統(tǒng)都 有進(jìn)程的概念蜀铲。一個(gè)進(jìn)程就是一個(gè)執(zhí)行中的程序, 而每一個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間、一組 系統(tǒng)資源钮孵。在進(jìn)程的概念中,每一個(gè)進(jìn)程的內(nèi)部數(shù) 據(jù)和狀態(tài)都是完全獨(dú)立的眼滤。

在Windows操作系統(tǒng)中一個(gè)進(jìn)程就是一個(gè)exe或者dll 程序巴席,它們相互獨(dú)立,互相也可以通信诅需,在 Android操作系統(tǒng)中進(jìn)程間的通信應(yīng)用也是很多 的漾唉。

線程

線程與進(jìn)程相似荧库,是一段完成某個(gè)特定功能的代 碼,是程序中單個(gè)順序控制的流程赵刑,但與進(jìn)程不同 的是分衫,同類的多個(gè)線程是共享一塊內(nèi)存空間和一組 系統(tǒng)資源。所以系統(tǒng)在各個(gè)線程之間切換時(shí)般此,開銷 要比進(jìn)程小的多蚪战,正因如此,線程被稱為輕量級(jí)進(jìn)程铐懊。一個(gè)進(jìn)程中可以包含多個(gè)線程邀桑。

主線程

Java程序至少會(huì)有一個(gè)線程,這就是主線程科乎,程序 啟動(dòng)后是由JVM創(chuàng)建主線程壁畸,程序結(jié)束時(shí)由JVM停 止主線程。主線程它負(fù)責(zé)管理子線程茅茂,即子線程的 啟動(dòng)捏萍、掛起、停止等等操作空闲。下圖所示是進(jìn)程令杈、 主線程和子線程的關(guān)系,其中主線程負(fù)責(zé)管理子線 程进副,即子線程的啟動(dòng)这揣、掛起、停止等操作

[圖]?進(jìn)程影斑、主線程和子線程關(guān)系

獲取主線程示例代碼如下:

// 獲取主線程

ThreadmainThread=Thread.currentThread();

System.out.println("主線程名:"+mainThread.getName());

創(chuàng)建子線程

Java中創(chuàng)建一個(gè)子線程涉及到:java.lang.Thread類 和java.lang.Runnable接口给赞。Thread是線程類,創(chuàng)建一個(gè)Thread對(duì)象就會(huì)產(chǎn)生一個(gè)新的線程矫户。而線程執(zhí)行的程序代碼是在實(shí)現(xiàn)Runnable接口對(duì)象的run()方法中編寫的片迅,實(shí)現(xiàn)Runnable接口對(duì)象是線程執(zhí)行對(duì) 象。線程執(zhí)行對(duì)象實(shí)現(xiàn)Runnable接口的run()方法皆辽,run() 方法是線程執(zhí)行的入口柑蛇,該線程要執(zhí)行程序代碼都 在此編寫的,run()方法稱為線程體驱闷。

提示 主線程中執(zhí)行入口是main(String[] args) 方法耻台,這里可以控制程序的流程,管理其他的 子線程等空另。子線程執(zhí)行入口是線程執(zhí)行對(duì)象 (實(shí)現(xiàn)Runnable接口對(duì)象)的run()方法盆耽,在這個(gè)方法可以編寫子線程相關(guān)處理代碼。

實(shí)現(xiàn)Runnable接口

創(chuàng)建線程Thread對(duì)象時(shí),可以將線程執(zhí)行對(duì)象傳遞 給它摄杂,這需要是使用Thread類如下兩個(gè)構(gòu)造方法:

Thread(Runnable target, String name):target是線程執(zhí)行對(duì)象坝咐,實(shí)現(xiàn)Runnable接口。name為線程 指定一個(gè)名字析恢。

Thread(Runnable target):target是線程執(zhí)行對(duì) 象墨坚,實(shí)現(xiàn)Runnable接口。線程名字是由JVM分配的映挂。

下面看一個(gè)具體示例泽篮,實(shí)現(xiàn)Runnable接口的線程執(zhí) 行對(duì)象Runner代碼如下:

publicclassRunnerimplementsRunnable{

?@Override

publicvoidrun() {

for(inti=0;i<10;i++) {

//打印次數(shù)和線程名稱

System.out.printf("第%d次執(zhí)行 - %s\n",i,Thread.currentThread().getName());

?longsleepTime=(long) (1000*Math.random());

try{

//線程休眠

Thread.sleep(sleepTime);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ?? }

? ? ?? }

//線程執(zhí)行結(jié)束

System.out.println("執(zhí)行完成! "+Thread.currentThread().getName());

?? }

}

代碼Thread.sleep(sleepTime)是休 眠當(dāng)前線程, sleep是靜態(tài)方法它有兩個(gè)版本:

static void sleep(long millis):在指定的毫秒數(shù)內(nèi) 讓當(dāng)前正在執(zhí)行的線程休眠。

static void sleep(long millis, int nanos) 在指定的毫 秒數(shù)加指定的納秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程 休眠袖肥。

測(cè)試程序代碼如下:

publicclassRunnerTest{

publicstaticvoidmain(String[]args) {

//創(chuàng)建線程t1,參數(shù)時(shí)一個(gè)線程執(zhí)行對(duì)象Runner

Threadt1=newThread(newRunner());

//開始線程t1

t1.start();

?

//創(chuàng)建線程t2,參數(shù)時(shí)一個(gè)線程執(zhí)行對(duì)象Runner

Threadt2=newThread(newRunner(),"這里指定線程名稱");

//開始線程t2

t2.start();

?

?? }

}

提示 仔細(xì)分析一下運(yùn)行結(jié)果咪辱,會(huì)發(fā)現(xiàn)兩個(gè)線 程是交錯(cuò)運(yùn)行的,感覺就像是兩個(gè)線程在同時(shí) 運(yùn)行椎组。但是實(shí)際上一臺(tái)PC通常就只有一顆 CPU,在某個(gè)時(shí)刻只能是一個(gè)線程在運(yùn)行历恐,而 Java語言在設(shè)計(jì)時(shí)就充分考慮到線程的并發(fā)調(diào) 度執(zhí)行寸癌。對(duì)于程序員來說,在編程時(shí)要注意給 每個(gè)線程執(zhí)行的時(shí)間和機(jī)會(huì)弱贼,主要是通過讓線 程休眠的辦法(調(diào)用sleep()方法)來讓當(dāng)前線 程暫停執(zhí)行蒸苇,然后由其他線程來爭(zhēng)奪執(zhí)行的機(jī)

會(huì)。如果上面的程序中沒有用到sleep()方法吮旅, 則就是第一個(gè)線程先執(zhí)行完畢溪烤,然后第二個(gè)線 程再執(zhí)行完畢。所以用活sleep()方法是多線程 編程的關(guān)鍵庇勃。

繼承Thread線程類

Thread類也實(shí)現(xiàn)了Runnable接口檬嘀,所以 Thread類也可以作為線程執(zhí)行對(duì)象,這需要繼承 Thread類责嚷,覆蓋run()方法鸳兽。

采用繼承Thread類重新實(shí)現(xiàn)Runner示例,自定義 線程類MyThread代碼如下:

publicclassMyThreadextendsThread{

publicMyThread() {

super();

?? }

?

publicMyThread(Stringname) {

super(name);

?? }

?

//程序執(zhí)行代碼

?

?

@Override

publicvoidrun() {

for(inti=0;i<10;i++) {

//打印次數(shù)和線程名稱

System.out.printf("第%d次執(zhí)行 - %s\n",i,Thread.currentThread().getName());

?

longsleepTime=(long) (1000*Math.random());

try{

//線程休眠

Thread.sleep(sleepTime);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ?? }

? ? ?? }

//線程執(zhí)行結(jié)束

System.out.println("執(zhí)行完成! "+getName());

?? }

}

通 過super調(diào)用父類Thread構(gòu)造方法罕拂,這兩個(gè)Thread類 構(gòu)造方法:

Thread(String name):name為線程指定一個(gè)名 字揍异。代碼第④行調(diào)用都就是此構(gòu)造方法。

Thread():線程名字是JVM分配的爆班。代碼第②行 調(diào)用都就是此構(gòu)造方法衷掷。

測(cè)試程序代碼如下:

//通過繼承Thread線程類來創(chuàng)建線程

//創(chuàng)建線程t1,參數(shù)時(shí)一個(gè)線程執(zhí)行對(duì)象Runner

Threadt3=newMyThread();

//開始線程t1

t3.start();

?

//創(chuàng)建線程t2,參數(shù)時(shí)一個(gè)線程執(zhí)行對(duì)象Runner

Threadt4=newMyThread("這里指定線程名稱");

//開始線程t4

t4.start();

提示 由于Java只支持單重繼承,繼承Thread 類的方式不能再繼承其他父類柿菩。當(dāng)開發(fā)一些圖 形界面的應(yīng)用時(shí)戚嗅,需要一個(gè)類既是一個(gè)窗口 (繼承JFrame)又是一個(gè)線程體,那么只能采 用實(shí)現(xiàn)Runnable接口方式。

使用匿名內(nèi)部類和Lambda表達(dá)式實(shí)現(xiàn)線程體

如果線程體使用的地方不是很多渡处,可以不用單獨(dú)定 義一個(gè)類镜悉。可以使用匿名內(nèi)部類或Lambda表達(dá)式直 接實(shí)現(xiàn)Runnable接口医瘫。Runnable中只有一個(gè)方法是 函數(shù)式接口侣肄,可以使用Lambda表達(dá)式。

代碼如下:

//使用匿名內(nèi)部類和Lambda表達(dá)式實(shí)現(xiàn)線 程體

//創(chuàng)建線程t5醇份,參數(shù)是實(shí)現(xiàn)Runnable接口的匿名內(nèi)部類

Threadt5=newThread(newRunnable() {

@Override

publicvoidrun() {

for(inti=0;i<10;i++) {

//打印次數(shù)和線程名稱

System.out.printf("第%d次執(zhí)行 - %s\n",i,Thread.currentThread().getName());

?

longsleepTime=(long) (1000*Math.random());

try{

//線程休眠

Thread.sleep(sleepTime);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ? ? ? ? ?? }

? ? ? ? ? ? ?? }

//線程執(zhí)行結(jié)束

System.out.println("執(zhí)行完成! "+Thread.currentThread().getName());

?

? ? ? ? ?? }

? ? ?? });

// 開始線程t5

t5.start();

?


//創(chuàng)建線程t6稼锅,參數(shù)是,參數(shù)是實(shí)現(xiàn)Runnable接口的Lambda表達(dá)式

Threadt6=newThread(()->{

for(inti=0;i<10;i++) {

//打印次數(shù)和線程名稱

System.out.printf("第%d次執(zhí)行 - %s\n",i,Thread.currentThread().getName());

?

longsleepTime=(long) (1000*Math.random());

try{

//線程休眠

Thread.sleep(sleepTime);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ? ? ? ? ?? }

? ? ? ? ? ? ?? }

//線程執(zhí)行結(jié)束

System.out.println("執(zhí)行完成! "+Thread.currentThread().getName());

},"Lambda表達(dá)式");

// 開始線程t6

t6.start();

上述代碼第①行采用匿名內(nèi)部類實(shí)現(xiàn)Runnable接 口僚纷,覆蓋run()方法矩距。這里使用的是Thread(Runnable target)構(gòu)造方法。代碼第②行采用Lambda表達(dá)式實(shí)

現(xiàn)Runnable接口怖竭,覆蓋run()方法锥债。這里使用的是 Thread(Runnable target, String name)構(gòu)造方法, Lambda表達(dá)式是它的第一個(gè)參數(shù)痊臭。匿名內(nèi)部類和 Lambda表達(dá)式代碼雖然很多哮肚,但是它只是一個(gè)參 數(shù),實(shí)現(xiàn)了Runnable接口線程執(zhí)行對(duì)象广匙,

提示 匿名內(nèi)部類和Lambda表達(dá)式不需要定 義一個(gè)線程類文件允趟,使用起來很方便。特別是 Lambda表達(dá)式使代碼變得非常簡潔鸦致。但是客 觀上匿名內(nèi)部類和Lambda表達(dá)式會(huì)使代碼可 讀性變差潮剪,對(duì)于初學(xué)者不容易理解。

線程的狀態(tài)

在線程的生命周期中分唾,線程會(huì)有幾種狀態(tài)抗碰,如圖 23-5所示,線程有5種狀態(tài)鳍寂。下面分別介紹一下改含。

新建狀態(tài)

新建狀態(tài)(New)是通過new等方式創(chuàng)建線程對(duì) 象,它僅僅是一個(gè)空的線程對(duì)象

就緒狀態(tài)

當(dāng)主線程調(diào)用新建線程的start()方法后迄汛,它就進(jìn) 入就緒狀態(tài)(Runnable)捍壤。此時(shí)的線程尚未真正 開始執(zhí)行run()方法,它必須等待CPU的調(diào)度鞍爱。

運(yùn)行狀態(tài)

CPU的調(diào)度就緒狀態(tài)的線程鹃觉,線程進(jìn)入運(yùn)行狀 態(tài)(Running),處于運(yùn)行狀態(tài)的線程獨(dú)占 CPU睹逃,執(zhí)行run()方法盗扇。

阻塞狀態(tài)因?yàn)槟撤N原因運(yùn)行狀態(tài)的線程會(huì)進(jìn)入不可運(yùn)行 狀態(tài)祷肯,即阻塞狀態(tài)(Blocked),處于阻塞狀態(tài)的線程JVM系統(tǒng)不能執(zhí)行該線程疗隶,即使CPU空 閑佑笋,也不能執(zhí)行該線程。如下幾個(gè)原因會(huì)導(dǎo)致 線程進(jìn)入阻塞狀態(tài):

當(dāng)前線程調(diào)用sleep()方法斑鼻,進(jìn)入休眠狀態(tài)蒋纬。

被其他線程調(diào)用了join()方法,等待其他線程 結(jié)束坚弱。

發(fā)出I/O請(qǐng)求蜀备,等待I/O操作完成期間。

當(dāng)前線程調(diào)用wait()方法荒叶。

處于阻塞狀態(tài)可以重新回到就緒狀態(tài)碾阁,如:休 眠結(jié)束、其他線程加入些楣、I/O操作完成和調(diào)用 notify或notifyAll喚醒wait線程脂凶。

死亡狀態(tài)

線程退出run()方法后,就會(huì)進(jìn)入死亡狀態(tài) (Dead)愁茁,線程進(jìn)入死亡狀態(tài)有可以是正常實(shí) 現(xiàn)完成run()方法進(jìn)入艰猬,也可能是由于發(fā)生異常 而進(jìn)入的。

線程管理

線程優(yōu)先級(jí)

線程的調(diào)度程序根據(jù)線程決定每次線程應(yīng)當(dāng)何時(shí)運(yùn) 行埋市,Java提供了10種優(yōu)先級(jí),分別用1~10整數(shù)表 示命贴,最高優(yōu)先級(jí)是10用常量MAX_PRIORITY表 示道宅;最低優(yōu)先級(jí)是1用常量MIN_PRIORITY;默認(rèn) 優(yōu)先級(jí)是5用常量NORM_PRIORITY表示胸蛛。

Thread類提供了setPriority(int newPriority)方法可以 設(shè)置線程優(yōu)先級(jí)污茵,通過getPriority()方法獲得線程優(yōu) 先級(jí)。

設(shè)置線程優(yōu)先級(jí)示例代碼如下:

//通過實(shí)現(xiàn)Runnable接口來創(chuàng)建線程

//創(chuàng)建線程t1,參數(shù)時(shí)一個(gè)線程執(zhí)行對(duì)象Runner

Threadt1=newThread(newRunner());

//設(shè)置線程優(yōu)先級(jí)10

t1.setPriority(Thread.MAX_PRIORITY);

//開始線程t1

t1.start();

?

//創(chuàng)建線程t2,參數(shù)時(shí)一個(gè)線程執(zhí)行對(duì)象Runner

Threadt2=newThread(newRunner(),"這里指定線程名稱");

//設(shè)置線程優(yōu)先級(jí)1

t1.setPriority(Thread.MIN_PRIORITY);

//開始線程t2

t2.start();

提示 多次運(yùn)行上面的示例會(huì)發(fā)現(xiàn)葬项,t1線程經(jīng) 常先運(yùn)行泞当,但是偶爾t2線程也會(huì)先運(yùn)行。這些 現(xiàn)象說明了:影響線程獲得CPU時(shí)間的因素民珍, 除了受到的線程優(yōu)先級(jí)外襟士,還與操作系統(tǒng)有 關(guān)。

?

等待線程結(jié)束

在介紹現(xiàn)在狀態(tài)時(shí)提到過join()方法嚷量,當(dāng)前線程調(diào)用 t1線程的join()方法陋桂,則阻塞當(dāng)前線程,等待t1線程 結(jié)束蝶溶,如果t1線程結(jié)束或等待超時(shí)嗜历,則當(dāng)前線程回到就緒狀態(tài)。

Thread類提供了多個(gè)版本的join(),它們定義如下:

void join():等待該線程結(jié)束梨州。

void join(long millis):等待該線程結(jié)束的時(shí)間最 長為millis毫秒痕囱。如果超時(shí)為0意味著要一直等下 去。

void join(long millis, int nanos):等待該線程結(jié)束 的時(shí)間最長為millis毫秒加nanos納秒暴匠。

使用join()方法示例代碼如下:

publicclassJoin{

staticintvalue=0;

?

publicstaticvoidmain(String[]args)throwsInterruptedException{

System.out.println("主線程開始");

?

//創(chuàng)建線程t,參數(shù)是一個(gè)線程執(zhí)行對(duì)象Runner

Threadt1=newThread(newRunnable() {

@Override

publicvoidrun() {

System.out.println("子開始");

for(inti=0;i<3;i++) {

System.out.println("子執(zhí)行");

value++;

? ? ? ? ? ? ?? }

System.out.println("子結(jié)束");

? ? ? ? ?? }

},"ThreadA");

//開始t線程

t1.start();

//主線程被阻塞,等待t1線程結(jié)束

t1.join();

System.out.println("value = "+value);

System.out.println("主線程結(jié)束");

?? }

}

?

結(jié)果:

提示 使用join()方法的場(chǎng)景是鞍恢,一個(gè)線程依賴 于另外一個(gè)線程的運(yùn)行結(jié)果,所以調(diào)用另一個(gè) 線程的join()方法等它運(yùn)行完成巷查。

線程讓步

線程類Thread還提供一個(gè)靜態(tài)方法yield()有序,調(diào)用 yield()方法能夠使當(dāng)前線程給其他線程讓步。它類 似于sleep()方法岛请,能夠使運(yùn)行狀態(tài)的線程放棄CPU 使用權(quán)旭寿,暫停片刻,然后重新回到就緒狀態(tài)崇败。與 sleep()方法不同的是盅称,sleep()方法是線程進(jìn)行休眠,能夠給其他線程運(yùn)行的機(jī)會(huì)后室,無論線程優(yōu)先級(jí) 高低都有機(jī)會(huì)運(yùn)行缩膝。而yield()方法只給相同優(yōu)先級(jí) 或更高優(yōu)先級(jí)線程機(jī)會(huì)。

示例代碼如下:

Thread.yield();

提示 yield()方法只能給相同優(yōu)先級(jí)或更高優(yōu) 先級(jí)的線程讓步岸霹,yield()方法在實(shí)際開發(fā)中很

少使用疾层,大多都使用sleep()方法,sleep()方法 可以控制時(shí)間贡避,而yield()方法不能痛黎。

?

?

線程停止

線程體中的run()方法結(jié)束,線程進(jìn)入死亡狀態(tài)刮吧,線 程就停止了湖饱。但是有些業(yè)務(wù)比較復(fù)雜,例如想開發(fā) 一個(gè)下載程序杀捻,每隔一段執(zhí)行一次下載任務(wù)井厌,下載 任務(wù)一般會(huì)在由子線程執(zhí)行的,休眠一段時(shí)間再執(zhí) 行致讥。這個(gè)下載子線程中會(huì)有一個(gè)死循環(huán)仅仆,但是為了 能夠停止子線程,設(shè)置一個(gè)結(jié)束變量拄踪。

示例下面如下:

publicclassStopThread{

privatestaticStringcommand="";

?

publicstaticvoidmain(String[]args) {

//創(chuàng)建線程t1蝇恶,參數(shù)是一個(gè)線程執(zhí)行對(duì)象Runner

Threadt1=newThread(newRunnable() {

@Override

publicvoidrun() {

// 一直循環(huán),直到滿足條件在停止線程

while(!"exit".equalsIgnoreCase(command)) {

// 線程開始工作

System.out.println("下載中...");

try{

//線程休眠

Thread.sleep(1000);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ? ? ? ? ? ? ?? }

? ? ? ? ? ? ?? }

//線程結(jié)束

System.out.println("執(zhí)行完成");

? ? ? ? ?? }

? ? ?? });

//開始線程t1

t1.start();

//輸入

//BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

try(BufferedReaderbr=newBufferedReader(newInputStreamReader(System.in))){

command=br.readLine();

}catch(IOExceptione) {

e.printStackTrace();

? ? ?? }

?? }

}

運(yùn)行結(jié)果:

提示 控制線程的停止有人會(huì)想到使用Thread 提供的stop()方法惶桐,這個(gè)方法已經(jīng)不推薦使用 了撮弧,這個(gè)方法有時(shí)會(huì)引發(fā)嚴(yán)重的系統(tǒng)故障潘懊,類似還是有suspend()和resume()掛起方法。Java現(xiàn)在推薦的做法就是采用本例的結(jié)束變量方式贿衍。

線程安全

多一個(gè)線程同時(shí)運(yùn)行授舟,有時(shí)線程之間需要共享數(shù) 據(jù),一個(gè)線程需要其他線程的數(shù)據(jù)贸辈,否則就不能保 證程序運(yùn)行結(jié)果的正確性释树。

例如有一個(gè)航空公司的機(jī)票銷售,每一天機(jī)票數(shù)量 是有限的擎淤,很多售票點(diǎn)同時(shí)銷售這些機(jī)票奢啥。下面是 一個(gè)模擬銷售機(jī)票系統(tǒng),示例代碼如下

publicclassTicketDB{

?

//機(jī)票數(shù)量

privateintticketCount=5;

?

//獲取當(dāng)前機(jī)票數(shù)量

publicintgetTicketCount(){

returnticketCount;

?? }

?

//銷售機(jī)票

publicvoidsellTicket(){

?

//等待用戶付款

//線程休眠

try{

Thread.sleep(1000);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ?? }

System.out.printf("第%d號(hào)票已經(jīng)售出\n",ticketCount);

ticketCount--;

?? }

?

調(diào)用代碼如下:

publicclassTicketTest{

publicstaticvoidmain(String[]args) {

Runnablerun=newTicket();

// 創(chuàng)建線程t1

Threadt1=newThread(run);

t1.start();

Threadt2=newThread(run);

t2.start();

?

?? }

?

staticclassTicketimplementsRunnable{

TicketDBdb=newTicketDB();

@Override

publicvoidrun() {

while(true){

intcurrentTC=db.getTicketCount();

//查詢是否有票

if(currentTC>0) {

db.sellTicket();

}else{

//無票退出

break;

? ? ? ? ? ? ?? }


? ? ? ? ?? }

?

? ? ?? }

?? }

}

一次運(yùn)行結(jié)果如下:

雖然可以能每次運(yùn)行的結(jié)果都不一樣嘴拢,但是從結(jié)果 看還是能發(fā)現(xiàn)一些問題:同一張票重復(fù)銷售桩盲、出現(xiàn) 第0號(hào)票和5張票賣了6次。這些問題的根本原因是 多個(gè)線程間共享的數(shù)據(jù)導(dǎo)致數(shù)據(jù)的不一致性席吴。

提示 多個(gè)線程間共享的數(shù)據(jù)稱為共享資源或 臨界資源赌结,由于是CPU負(fù)責(zé)線程的調(diào)度,程序

員無法精確控制多線程的交替順序孝冒。這種情況 下柬姚,多線程對(duì)臨界資源的訪問有時(shí)會(huì)導(dǎo)致數(shù)據(jù) 的不一致性。

?

多線程同步

為了防止多線程對(duì)臨界資源的訪問有時(shí)會(huì)導(dǎo)致數(shù)據(jù) 的不一致性庄涡,Java提供了“互斥”機(jī)制量承,可以為這些 資源對(duì)象加上一把“互斥鎖”,在任一時(shí)刻只能由一 個(gè)線程訪問穴店,即使該線程出現(xiàn)阻塞宴合,該對(duì)象的被鎖 定狀態(tài)也不會(huì)解除,其他線程仍不能訪問該對(duì)象迹鹅, 這就多線程同步。線程同步保證線程安全的重要手 段贞言,但是線程同步客觀上會(huì)導(dǎo)致性能下降斜棚。可以通過兩種方式實(shí)現(xiàn)線程同步该窗,兩種方式都涉及 到使用synchronized關(guān)鍵字弟蚀,一種是synchronized方 法,使用synchronized關(guān)鍵字修飾方法酗失,對(duì)方法進(jìn) 行同步义钉;另一種是synchronized語句,使用 synchronized關(guān)鍵字放在對(duì)象前面限制一段代碼的 執(zhí)行规肴。

synchronized方法

synchronized關(guān)鍵字修飾方法實(shí)現(xiàn)線程同步捶闸,方 法所在的對(duì)象被鎖定夜畴,修改售票系統(tǒng)示 例, TicketDB.java文件代碼如下:

publicclassTicketDB{

?

//機(jī)票數(shù)量

privateintticketCount=5;

?

//獲取當(dāng)前機(jī)票數(shù)量

publicsynchronizedintgetTicketCount(){

returnticketCount;

?? }


//銷售機(jī)票

publicsynchronizedvoidsellTicket(){

?

//等待用戶付款

//線程休眠

try{

Thread.sleep(1000);

}catch(InterruptedExceptione) {

e.printStackTrace();

? ? ?? }

System.out.printf("第%d號(hào)票已經(jīng)售出\n",ticketCount);

ticketCount--;

?? }

?

?

}

?

上述代碼第①行和第②行的方法前都使用了 synchronized關(guān)鍵字删壮,表明這兩個(gè)方法是同步 的贪绘,被鎖定的,每一個(gè)時(shí)刻只能由一個(gè)線程訪 問央碟。并不是每一個(gè)方法都有必要加鎖的税灌,要仔 細(xì)研究加上的必要性,上述代碼第①行加鎖可

? 以防止出現(xiàn)第0號(hào)票情況和5張票賣出6次的情況亿虽;代碼第②行加鎖是防止出現(xiàn)銷售兩種一樣 的票

?

synchronized語句

synchronized語句方式主要用于第三方類菱涤,不方 便修改它的代碼情況。同樣是售票系統(tǒng)示例洛勉,可以不用修改TicketDB.java類粘秆,只修改調(diào)用代碼TicketTest.java實(shí)現(xiàn)同步。

代碼如下:

publicclassTicketTest{

?

publicstaticvoidmain(String[]args) {

?

// ? ? ?? Runnable run = new Ticket();

// ? ? ?? new Thread(run).start();

// ? ? ?? new Thread(run).start();

?

// 創(chuàng)建線程t1

Runnablerun=newTicket();

Threadt1=newThread(run);

t1.start();

Threadt2=newThread(run);

t2.start();

?? }

?

staticclassTicketimplementsRunnable{

TicketDBdb=newTicketDB();

@Override

publicvoidrun() {

while(true){

//同步代碼語句

synchronized(db) {

intcurrentTC=db.getTicketCount();

//查詢是否有票

if(currentTC>0) {

db.sellTicket();

}else{

//無票退出

break;

? ? ? ? ? ? ? ? ?? }

? ? ? ? ? ? ?? }

? ? ? ? ?? }

?

? ? ?? }

?? }

}

?

將需要同步的代碼用大括號(hào)括起來坯认。 synchronized后有小括號(hào)翻擒,將需要同步的對(duì)象括 起來。

線程間通信

上面的示例只是簡單地為特定對(duì)象或方法加 鎖牛哺,但有時(shí)情況會(huì)更加復(fù)雜陋气,如果兩個(gè)線程之間有 依賴關(guān)系,線程之間必須進(jìn)行通信引润,互相協(xié)調(diào)才能 完成工作巩趁。例如有一個(gè)經(jīng)典的堆棧問題,一個(gè)線程生成了一些 數(shù)據(jù)淳附,將數(shù)據(jù)壓棧议慰;另一個(gè)線程消費(fèi)了這些數(shù)據(jù), 將數(shù)據(jù)出棧奴曙。這兩個(gè)線程互相依賴别凹,當(dāng)堆棧為空 時(shí),消費(fèi)線程無法取出數(shù)據(jù)時(shí)洽糟,應(yīng)該通知生成線程 添加數(shù)據(jù)炉菲;當(dāng)堆棧已滿時(shí),生產(chǎn)線程無法添加數(shù)據(jù) 時(shí)坤溃,應(yīng)該通知消費(fèi)線程取出數(shù)據(jù)拍霜。為了實(shí)現(xiàn)線程間通信,需要使用Object類中聲明的5 個(gè)方法:

void wait():使當(dāng)前線程釋放對(duì)象鎖薪介,然后當(dāng)前 線程處于對(duì)象等待隊(duì)列中阻塞狀態(tài)祠饺,如下圖所 示,等待其他線程喚醒汁政。

void wait(long timeout):同wait()方法道偷,等待 timeout毫秒時(shí)間缀旁。

void wait(long timeout, int nanos):同wait()方法,等待timeout毫秒加nanos納秒時(shí)間试疙。

void notify():當(dāng)前線程喚醒此對(duì)象等待隊(duì)列中 的一個(gè)線程诵棵,如下圖所示該線程將進(jìn)入就緒狀 態(tài)。

void notifyAll():當(dāng)前線程喚醒此對(duì)象等待隊(duì)列 中的所有線程祝旷,如下圖所示這些線程將進(jìn)入就 緒狀態(tài)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末履澳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子怀跛,更是在濱河造成了極大的恐慌距贷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吻谋,死亡現(xiàn)場(chǎng)離奇詭異忠蝗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)漓拾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門阁最,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人骇两,你說我怎么就攤上這事速种。” “怎么了低千?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵配阵,是天一觀的道長。 經(jīng)常有香客問我示血,道長棋傍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任难审,我火速辦了婚禮瘫拣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘告喊。我一直安慰自己拂铡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布葱绒。 她就那樣靜靜地躺著,像睡著了一般斗锭。 火紅的嫁衣襯著肌膚如雪地淀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天岖是,我揣著相機(jī)與錄音帮毁,去河邊找鬼实苞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛烈疚,可吹牛的內(nèi)容都是我干的黔牵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼爷肝,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼猾浦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灯抛,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤金赦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后对嚼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體夹抗,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年纵竖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漠烧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡靡砌,死狀恐怖已脓,靈堂內(nèi)的尸體忽然破棺而出盯桦,到底是詐尸還是另有隱情廷区,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布噪服,位于F島的核電站邓了,受9級(jí)特大地震影響恨诱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骗炉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一照宝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧句葵,春花似錦厕鹃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至轻专,卻和暖如春忆矛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國打工催训, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洽议,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓漫拭,卻偏偏與公主長得像亚兄,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子采驻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355