面向?qū)ο骿ava程序設(shè)計(jì)之多線程精講_第1頁
面向?qū)ο骿ava程序設(shè)計(jì)之多線程精講_第2頁
面向?qū)ο骿ava程序設(shè)計(jì)之多線程精講_第3頁
面向?qū)ο骿ava程序設(shè)計(jì)之多線程精講_第4頁
面向?qū)ο骿ava程序設(shè)計(jì)之多線程精講_第5頁
已閱讀5頁,還剩47頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第十一講多線程線程基礎(chǔ)Java語言的線程Java線程的生存期★★鎖與線程同步★★★線程阻塞★★★什么是線程?

Definition1:Athreadisasinglesequentialflowofcontrolwithinaprogram.

Definition2:AbasicunitofCPUutilization,andconsistsofaprogramcounter,aregisterset,andstackspace.Itshareswithpeerthreadsitscodesection,datasection,andoperatingsystemresources.11.1線程基礎(chǔ)多線程vs

處理器

可由多個(gè)硬件處理器同時(shí)支持多線程。

也可由單個(gè)硬件處理器以時(shí)間分片方式支持多線程。

還可由多個(gè)硬件處理器以時(shí)間分片方式支持多線程。使用多線程的理由

①多線程可滿足問題域本質(zhì)上的并發(fā)性需求

并發(fā)問題最好以并發(fā)方式處理。

②可充分利用多處理器體系結(jié)構(gòu)以提高應(yīng)用程序性能

每一線程可在不同處理器上并發(fā)執(zhí)行。

③更方便地實(shí)現(xiàn)計(jì)算任務(wù)之間的資源共享

同一進(jìn)程中的各線程共享該進(jìn)程的內(nèi)存與其他資源。

④提高交互式應(yīng)用程序的響應(yīng)性(responsiveness)

多線程交互程序執(zhí)行長操作時(shí)可繼續(xù)響應(yīng)用戶的請(qǐng)求。

⑤比使用進(jìn)程更合算

線程上下文切換較之進(jìn)程的開銷更低(不必分配內(nèi)存或資源)。

線程之間的通信也比進(jìn)程間通信更有效(簡單且高效)。

⑥在分布式計(jì)算中服務(wù)程序通常采用多線程

從而提高服務(wù)程序的性能。為什么使用多線程?AllnontrivialapplicationsfortheJavaplatformaremultithreaded,whetheryoulikeitornot.使用多線程的代價(jià)

①多線程應(yīng)用程序遠(yuǎn)比單線程應(yīng)用程序復(fù)雜

程序員還需仔細(xì)處理多個(gè)計(jì)算任務(wù)之間的通信與同步。

②多線程應(yīng)用程序有更多的設(shè)計(jì)要求

并發(fā)性本質(zhì)上就有相當(dāng)?shù)膹?fù)雜性:同步、死鎖、公平(饑餓)、...

③多線程應(yīng)用程序更加難以調(diào)試和測試

應(yīng)用程序的不確定性導(dǎo)致這一困難。

④多線程應(yīng)用程序更難于實(shí)現(xiàn)

需掌握某一特定線程庫的具體用法。

⑤多線程應(yīng)用程序的行為通常與特定平臺(tái)有關(guān)

導(dǎo)致應(yīng)用程序的平臺(tái)可移植性降低。進(jìn)程──重量級(jí)任務(wù)

①每一進(jìn)程占用獨(dú)立的地址空間。

此處的地址空間包括代碼、數(shù)據(jù)及其他資源。

②進(jìn)程間的通信開銷較大且受到許多限制。

對(duì)象(或函數(shù))接口、通信協(xié)議、...

③進(jìn)程間的切換開銷也較大。

又稱上下文切換(contextswitch)。

上下文包括代碼、數(shù)據(jù)、堆棧、處理器狀態(tài)、資源、...線程──輕量級(jí)任務(wù)

①多個(gè)線程共享進(jìn)程的地址空間(代碼、數(shù)據(jù)、其他資源等)。

線程也需要自己的資源,如程序計(jì)數(shù)器、寄存器組、調(diào)用棧等。

②線程間的通信開銷較少且比較簡單。

因?yàn)楣蚕矶鴾p少了需要通信的內(nèi)容。

但也因?yàn)槌浞止蚕矶鵁o法對(duì)共享資源進(jìn)行保護(hù)。

③線程間的切換開銷也較小。

只需保存每一線程的程序計(jì)數(shù)器、寄存器組、堆棧等空間。

不必切換或復(fù)制整個(gè)地址空間,從而成本大為降低(約1/10)。線程vs

進(jìn)程Java線程機(jī)制建立在宿主操作系統(tǒng)的線程基礎(chǔ)上

將宿主操作系統(tǒng)的線程機(jī)制包裝為語言一級(jí)的機(jī)制提供給程序員使用。

對(duì)上:為程序員提供簡單一致、獨(dú)立于平臺(tái)的多線程編程接口。

對(duì)下:為程序員屏蔽宿主操作系統(tǒng)線程機(jī)制的細(xì)節(jié)。

不同宿主操作系統(tǒng)的線程機(jī)制有差別。

Solaris平臺(tái)上:

線程要么執(zhí)行結(jié)束,要么被更高優(yōu)先級(jí)線程搶占(pre-empt)。

Windows98/NT平臺(tái):

相同優(yōu)先級(jí)的線程以相同時(shí)間片段(quantum)輪流運(yùn)行。

這一機(jī)制又稱時(shí)間分片(timeslicing)。

與Solaris類似,也支持高優(yōu)先級(jí)線程的搶占。Java的線程機(jī)制Java線程的實(shí)現(xiàn)

將Java平臺(tái)的線程機(jī)制映射到本地的線程庫實(shí)現(xiàn)。

Java應(yīng)用程序員不必關(guān)心如何映射到宿主操作系統(tǒng)的線程庫。

這一任務(wù)是由JVM供應(yīng)商完成的。

Java應(yīng)用程序也可通過JNI訪問本地線程庫。

但絕大多數(shù)情況下都不主張程序員這樣做。Java線程內(nèi)核層線程用戶層線程線程vsJVM

如果應(yīng)用程序中僅有一個(gè)線程,程序員無需顯式地處理線程。

由JVM自動(dòng)完成對(duì)單線程的管理。

正如之前的大多數(shù)Java應(yīng)用程序所示。

JVM本身是一個(gè)進(jìn)程。

一個(gè)JVM可同時(shí)支持多個(gè)線程。

JVM本身總是多線程的。

總存在一些守護(hù)線程(daemonthread)。

例如:垃圾收集線程、鼠標(biāo)與鍵盤事件分派線程、...。

每一個(gè)JVM有一個(gè)堆;每一個(gè)線程有一個(gè)棧。

從而一個(gè)JVM中的所有線程可通過共享的棧交換信息。使用以下Java接口或類

接口java.lang.Runnable

類java.lang.Thread11.2Java語言的線程Step1:編寫一個(gè)線程類

程序員自定義的類:

①要么實(shí)現(xiàn)java.lang.Runnable接口。

更好的面向?qū)ο笤O(shè)計(jì),提供一致的設(shè)計(jì)風(fēng)格。

可節(jié)約Java語言寶貴的單繼承指標(biāo)。

例如,一個(gè)Applet程序必須選擇這一途徑實(shí)現(xiàn)多線程。

②要么繼承java.lang.Thread類。

該類實(shí)現(xiàn)了Runnable接口,但run()被定義為空的。

可用更簡單的代碼完成同樣的事情。

線程類中最重要的重定義方法:run()。

通過重定義該方法完成線程本身要執(zhí)行的任務(wù)。創(chuàng)建線程Step2:創(chuàng)建線程類的一個(gè)對(duì)象實(shí)例

①如果線程類實(shí)現(xiàn)Runnable接口:

首先,創(chuàng)建該線程類的一個(gè)實(shí)例。

然后,再以上述實(shí)例為參數(shù)創(chuàng)建Thread的一個(gè)實(shí)例。

②如果線程類繼承Thread類:

直接創(chuàng)建線程類的一個(gè)實(shí)例。

Thread類的構(gòu)造方法有3個(gè)參數(shù),共7種組合:

①一個(gè)Runnable對(duì)象實(shí)例

該對(duì)象的run()方法將在線程中執(zhí)行。

②一個(gè)String對(duì)象實(shí)例

用于標(biāo)識(shí)線程。

③一個(gè)ThreadGroup對(duì)象實(shí)例

將線程指派到某一線程組;線程組將相關(guān)線程組織在一起。Step3:調(diào)用對(duì)象實(shí)例的start()方法

該方法創(chuàng)建并運(yùn)行線程。

申請(qǐng)線程運(yùn)行所需系統(tǒng)資源、調(diào)度線程、調(diào)用線程的run()方法。

當(dāng)該方法返回時(shí),線程已開始運(yùn)行并處于Runnable狀態(tài)。

由于多數(shù)計(jì)算機(jī)只有一個(gè)處理器,JVM必須調(diào)度線程。packagejava.lang;public

interfaceRunnable{

public

abstract

voidrun();}//通過實(shí)現(xiàn)Runnable接口定義新線程classCounterimplementsRunnable{

inti;

public

voidrun(){ i=0;

while(true){ System.out.println("計(jì)數(shù)器="+i++);

if(i==100)break; } }}public

classRunnableThread{

public

static

voidmain(String[]args){ Countercounter=newCounter(); Threadthread=newThread(counter); thread.start(); System.out.println("主程序結(jié)束。"); }}例子程序一實(shí)現(xiàn)Runnable接口packagejava.lang;public

classThreadimplementsRunnable{ ...;

public

voidrun(){

if(target!=null)target.run(); } ...}//通過繼承Thread類定義新線程public

classSubclassThreadextendsThread{

public

voidrun(){

while(true){ //執(zhí)行線程自身的任務(wù)

try{ sleep(1000);

break; }catch(InterruptedExceptionexc){ //睡眠被中斷

} } }

public

static

voidmain(String[]args){ Threadt=newSubclassThread(); t.start(); System.out.println("主程序結(jié)束。"); }}例子程序二繼承Thread類classCountThreadextendsThread{

private

intfrom;

private

intto;

publicCountThread(intfrom,intto){

this.from=from;

this.to=to; }

public

voidrun(){ System.out.println(this.getName()+"啟動(dòng)

...");

for(intindex=from;index<=to;index++){ System.out.print(index+""); } System.out.println(this.getName()+"停止。"); }}public

classTwoThreads{

static

public

voidmain(String[]args){ CountThreadthread1=newCountThread(1,10); CountThreadthread2=newCountThread(20,30); thread1.start(); thread2.start(); }}創(chuàng)建兩個(gè)新的線程程序輸出有不確定性如果計(jì)數(shù)量少,好象是兩個(gè)線程依次執(zhí)行放大看(成千上萬),它們卻是交錯(cuò)執(zhí)行Thread定義了getName()和setName()主線程的特點(diǎn)

當(dāng)Java程序啟動(dòng)時(shí),一個(gè)主線程自動(dòng)地被創(chuàng)建并運(yùn)行。

主線程是產(chǎn)生應(yīng)用程序所有其他線程的線程。

主線程通常最后執(zhí)行完畢,以完成各種收尾任務(wù)。

如何獲取與使用主線程?

調(diào)用Thread類的類方法currentThread()。

一旦獲得主線程對(duì)象實(shí)例后,其操作與其他線程無異。

默認(rèn)情況下,主線程名為main,優(yōu)先級(jí)為5,非監(jiān)控線程。

main也同時(shí)是一個(gè)線程組的名字。主線程//獲取主線程并調(diào)用主線程的方法public

classMainThread{

public

static

voidmain(String[]args){ Threadthread=Thread.currentThread(); System.out.println("線程信息:"+thread); System.out.println("線程名字:"+thread.getName());

thread.setName("MyThread"); System.out.println("新的名字:"+thread.getName());

try{

for(intindex=1;index<=10;index++){ System.out.print(index);

thread.sleep(1000); } }catch(InterruptedExceptionexc){ System.out.println("main()被中斷!"); } }}Threadinfo:Thread[main,5,main]Name:mainNewname:MyThread12345678910(線程名字,優(yōu)先級(jí),線程組)//一個(gè)貪婪的線程,可能獨(dú)占所有CPU時(shí)間public

classGreedyThreadextendsThread{

intnumber;

publicGreedyThread(intnumber){

this.number=number; }

public

voidrun(){

for(intindex=1;index<=100000;index++){

if((index%10000)==0){ System.out.println("線程#"+number+":"+index); } } }

static

public

voidmain(String[]args){

newGreedyThread(1).start();

newGreedyThread(2).start(); }}這種在run()方法定義大循環(huán)的線程又稱自私(selfish)線程事件處理的線程

事件處理代碼在一個(gè)單獨(dú)線程(event-dispatchingthread)中執(zhí)行。

從而保證事件處理程序可在下一處理程序開始之前執(zhí)行完畢。

組件繪制代碼也在event-dispatchingthread中執(zhí)行。

這意味著執(zhí)行事件處理代碼時(shí),GUI被凍結(jié)。

即既不會(huì)重新繪制,也不會(huì)響應(yīng)鼠標(biāo)或鍵盤輸入。

事件處理程序設(shè)計(jì)原則:

事件處理代碼應(yīng)能盡快地執(zhí)行完畢,

否則你的程序感覺起來性能很差!

如果事件處理時(shí)真的需要執(zhí)行長時(shí)間操作,

啟動(dòng)另一線程或?qū)⒄?qǐng)求轉(zhuǎn)發(fā)給另一線程。Swing的事件處理模型importjavax.swing.*;importjava.awt.event.*;publicclassFreezeEDTextendsJFrameimplementsActionListener{privateJButtonfreezer;publicFreezeEDT(){super("Freeze");freezer=newJButton("Freeze");freezer.addActionListener(this);add(freezer);pack();}publicvoidactionPerformed(ActionEvente){try{Thread.sleep(4000);}catch(InterruptedExceptionevt){}}publicstaticvoidmain(String...args){SwingUtilities.invokeLater(newRunnable(){publicvoidrun(){FreezeEDTedt=newFreezeEDT();edt.setVisible(true);}});}}importjavax.swing.*;importjava.awt.event.*;publicclassFreezeEDTextendsJFrameimplementsActionListener{privateJButtonfreezer;publicFreezeEDT(){super("Freeze");freezer=newJButton("Freeze");freezer.addActionListener(this);add(freezer);pack();}publicvoidactionPerformed(ActionEvente){newThread(newRunnable(){publicvoidrun(){try{Thread.sleep(4000);}catch(InterruptedExceptionevt){}finalStringhot="Hot";SwingUtilities.invokeLater(newRunnable(){publicvoidrun(){freezer.setText(hot);}});}}).start();}

publicstaticvoidmain(String...args){SwingUtilities.invokeLater(newRunnable(){publicvoidrun(){FreezeEDTedt=newFreezeEDT();edt.setVisible(true);}});}}publicclassSellTicketsTester{publicstaticvoidmain(String[]args){SellTicketst=newSellTickets();//新建一個(gè)售票類的對(duì)象

newThread(t).start();//用此對(duì)象作為參數(shù)創(chuàng)建3個(gè)新線程并啟動(dòng)

newThread(t).start();newThread(t).start();}}classSellTicketsimplementsRunnable{privateinttickets=200;//將共享的資源作為私有變量

publicvoidrun(){while(tickets>0)//直到?jīng)]有票可售為止

{System.out.println(Thread.currentThread().getName()+"issellingticket"+tickets--);}}}線程間的數(shù)據(jù)共享線程狀態(tài)轉(zhuǎn)換略圖11.3Java線程的生存期interrupt()interrupt()yield()orI/OorI/O結(jié)束線程狀態(tài)轉(zhuǎn)換詳圖線程的狀態(tài)

New:線程對(duì)象實(shí)例已創(chuàng)建,但尚未調(diào)用該實(shí)例的start()方法。

此時(shí)線程尚未獲取運(yùn)行所需的任何系統(tǒng)資源。

Runnable:線程已調(diào)用了start()方法,可以被調(diào)度執(zhí)行。

此時(shí)線程已獲得運(yùn)行時(shí)所需的系統(tǒng)資源。

注意此時(shí)線程并不是正在執(zhí)行,僅是具備被調(diào)度執(zhí)行條件而已。

Running:線程被JVM的線程調(diào)度程序分配了CPU執(zhí)行時(shí)間。

線程正在執(zhí)行,隨時(shí)可能由JVM線程調(diào)度程序送回Runnable狀態(tài)。

Dead:線程被JVM的線程調(diào)度程序分配了CPU執(zhí)行時(shí)間。

處于Dead狀態(tài)的線程不會(huì)再被調(diào)度執(zhí)行。

Blocked:正在運(yùn)行的線程(Running狀態(tài))因各種原因被阻塞。

例如,線程進(jìn)入synchronized代碼段時(shí)未能申請(qǐng)到想要的鎖。

例如,線程調(diào)用wait()等待另一線程同步。

例如,線程調(diào)用sleep()進(jìn)入睡眠狀態(tài)。

例如,線程調(diào)用join()等待另一線程執(zhí)行完畢。

所有情況的阻塞都有相應(yīng)的事件使線程回到Running狀態(tài)。線程狀態(tài)轉(zhuǎn)換在某狀態(tài)執(zhí)行不合法的操作均導(dǎo)致IllegalThreadStateException非阻塞的狀態(tài)轉(zhuǎn)換

NewRunnable:調(diào)用線程的start()方法。

這標(biāo)志著一個(gè)線程的整個(gè)生存期的開始。

RunnableRunning:由JVM的線程調(diào)度程序負(fù)責(zé)管理。

注意這一雙向狀態(tài)轉(zhuǎn)換主要由調(diào)度程序負(fù)責(zé),而不是應(yīng)用程序。

RunningDead:線程的run()方法執(zhí)行完畢或異常返回。

這標(biāo)志著一個(gè)線程的整個(gè)生存期的結(jié)束。

不主張使用線程實(shí)例的stop()方法顯式終止線程執(zhí)行。yield()方法

應(yīng)用程序也可簡單地參與線程調(diào)度:

調(diào)用Thread類yield()方法暫停線程,從而允許其他線程執(zhí)行。

此時(shí)線程切換為由執(zhí)行狀態(tài)轉(zhuǎn)換為可執(zhí)行狀態(tài)。

等待JVM線程調(diào)度程序再次將線程轉(zhuǎn)換為運(yùn)行狀態(tài)。

Java線程機(jī)制并不保證優(yōu)先級(jí)高的線程搶占CPU時(shí)間。

例如,工作在Solaris或MacOS等非搶占型平臺(tái)上的綠色線程。

使用yield()方法有助于提高應(yīng)用程序的可移植性。//一個(gè)貪婪的線程,可能獨(dú)占所有CPU時(shí)間public

classGreedyThreadextendsThread{

intnumber;

publicGreedyThread(intnumber){

this.number=number; }

public

voidrun(){

for(intindex=1;index<=100000;index++){

if((index%10000)==0){ System.out.println("線程#"+number+":"+index); } } }

static

public

voidmain(String[]args){

newGreedyThread(1).start();

newGreedyThread(2).start(); }}這種在run()方法定義大循環(huán)的線程又稱自私(selfish)線程>javaGreedyThread線程#1:10000線程#1:20000線程#1:30000線程#1:40000線程#1:50000線程#1:60000線程#1:70000線程#1:80000線程#1:90000線程#1:100000線程#2:10000線程#2:20000線程#2:30000線程#2:40000線程#2:50000線程#2:60000線程#2:70000線程#2:80000線程#2:90000線程#2:100000//一個(gè)禮貌的線程,經(jīng)常將CPU機(jī)會(huì)讓給其他線程public

classPoliteThreadextendsThread{

intnumber;

publicPoliteThread(intnumber){

this.number=number; }

public

voidrun(){

for(intindex=1;index<=100000;index++){

if((index%10000)==0){ System.out.println("線程#"+number+":"+index); yield(); } } }

static

public

voidmain(String[]args){

newPoliteThread(1).start();

newPoliteThread(2).start(); }}程序員應(yīng)盡量編寫這一類有禮貌的線程在run()方法循環(huán)體中主動(dòng)調(diào)用yield()方法>javaPoliteThread線程#1:10000線程#2:10000線程#1:20000線程#2:20000線程#1:30000線程#2:30000線程#1:40000線程#2:40000線程#1:50000線程#2:50000線程#1:60000線程#2:60000線程#1:70000線程#2:70000線程#1:80000線程#2:80000線程#1:90000線程#2:90000線程#1:100000線程#2:100000共享資源的訪問

當(dāng)兩個(gè)線程對(duì)同一共享資源并發(fā)訪問時(shí)可能帶來問題。

特別是當(dāng)一個(gè)或多個(gè)線程對(duì)資源進(jìn)行“寫”操作時(shí)。

多線程應(yīng)用程序的復(fù)雜性主要體現(xiàn)在對(duì)共享資源訪問的處理。

例如,以下兩個(gè)任務(wù)以不同交錯(cuò)次序執(zhí)行時(shí)最終帳戶余額是相同的嗎?

TellerA

TellerB

Getsbalance Getsbalance

Adds$100 Subtracts$100

Storesbalance Storesbalance11.4鎖與線程同步交錯(cuò)執(zhí)行的單位不是程序中的語句而是字節(jié)碼的語句對(duì)象的鎖標(biāo)志

每一個(gè)對(duì)象實(shí)例都有一個(gè)標(biāo)志。

該標(biāo)志可理解為一個(gè)鎖。

每一對(duì)象實(shí)例都只有唯一的一個(gè)鎖。

類似對(duì)象實(shí)例,每一類對(duì)象也有一個(gè)鎖。

保留字synchronized允許與鎖標(biāo)志進(jìn)行交互!

在進(jìn)入synchronized代碼段時(shí)必須獲取鎖(上鎖)。

synchronized代碼段執(zhí)行完畢或異常中斷時(shí)歸還鎖(解鎖)。

鎖用于控制臨界區(qū)的訪問。

即保證特定代碼段的互斥。

鎖保證了線程是否有權(quán)限進(jìn)入某一特定代碼段。對(duì)象實(shí)例的鎖//一個(gè)例子public

classMyStack{

inttop=-1;

char[]data=new

char[128];

public

voidpush(charelement){ top++; data[top]=element; }

public

charpop(){

charelement=data[top]; top--;

returnelement; }}當(dāng)兩個(gè)線程調(diào)用MyStack的同一實(shí)例的方法時(shí)會(huì)產(chǎn)生問題this對(duì)象未上鎖代碼(行為)數(shù)據(jù)(狀態(tài))public

voidpush(charelement){ //線程試圖執(zhí)行synchronized(this)時(shí)

//this對(duì)象未上鎖,則執(zhí)行后上鎖

synchronized(this){ top++; data[top]=element; } //synchronized塊執(zhí)行完后解鎖}this對(duì)象已上鎖代碼(行為)數(shù)據(jù)(狀態(tài))public

charpop(){ //線程試圖執(zhí)行synchronized(this)時(shí)

//this對(duì)象已上鎖,則須等待解鎖

synchronized(this){

charelement=data[top]; top--;

returnelement; }}另一種譬喻是獲取鎖即上鎖相當(dāng)于鎖消失以下兩段代碼的作用相同public

voidpush(charc){

synchronized(this){ ... }}public

synchronized

voidpush(charc){ ...}指定獲取哪一個(gè)對(duì)象實(shí)例的鎖;也可以是一個(gè)類名字,表示該類的鎖Java的鎖是可再入的

如果不支持可再入,則如下例子將導(dǎo)致死鎖!public

classReentrant{

public

synchronized

voida(){ b(); System.out.println("在a()中調(diào)用..."); }

public

synchronized

voidb(){ System.out.println("在b()中調(diào)用..."); }

public

static

voidmain(String[]args){

newReentrant().a(); }}類似對(duì)象實(shí)例,每一“類對(duì)象”也有一個(gè)鎖

故可利用synchronized修飾類方法以實(shí)現(xiàn)對(duì)類變量的同步訪問。public

classSynchronizedValue{

intx;

public

voidincrement(){

synchronized(SynchronizedValue.class){ x++; } }

public

voiddecrement(){

synchronized(SynchronizedValue.class){ x--; } }}線程設(shè)計(jì)的基本原則

①所有對(duì)敏感數(shù)據(jù)的訪問都應(yīng)使用synchronized。

如果所有方法對(duì)數(shù)據(jù)只讀,則該數(shù)據(jù)通常不是敏感的。

②由synchronized保護(hù)的敏感數(shù)據(jù)的訪問控制應(yīng)定義為private的。

從而數(shù)據(jù)只能由該類的方法直接訪問。

從而我們只需對(duì)該類的方法提供一種同步機(jī)制。

③同步粒度的設(shè)計(jì)不宜太大。

一個(gè)同步單元(粒度)太大會(huì)影響程序執(zhí)行效率。

通常將那些真正需要訪問共享數(shù)據(jù)的代碼才放在同步代碼段中。線程同步設(shè)計(jì)設(shè)計(jì)正確的多線程應(yīng)用程序

①標(biāo)識(shí)可能由多個(gè)線程同時(shí)訪問的共享數(shù)據(jù)

這可能是設(shè)計(jì)多線程應(yīng)用程序的最重要事情。

這些數(shù)據(jù)通常定義了應(yīng)用程序的臨界區(qū)(criticalregion)。

②保證共享數(shù)據(jù)不被多個(gè)線程同時(shí)訪問

須保證訪問共享數(shù)據(jù)的各線程之間互斥(mutualexclusion)。

如果未正確處理互斥,可能導(dǎo)致不見預(yù)見的結(jié)果。

互斥通常利用數(shù)據(jù)上的鎖來實(shí)現(xiàn)。

鎖用于避免同時(shí)的并發(fā)訪問。//一個(gè)線程安全的程序public

classThreadSafeTeller{

privateintbalance=0;

privatebooleansmile=false;

synchronized

voidcredit(intcash){ balance+=cash; smile=(balance>10000); }

synchronized

voiddebit(intcash){ balance-=cash; smile=(balance>10000); }}sleep()方法join()方法wait()/notify()方法11.5線程阻塞sleep()方法

指定在某一段時(shí)間(以毫秒為單位)內(nèi)線程處于阻塞狀態(tài)。

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

RunningBlocked:調(diào)用一個(gè)線程的sleep()方法。

BlockedRunning:時(shí)間事件。

經(jīng)過指定時(shí)間段后,線程又自動(dòng)恢復(fù)為可執(zhí)行狀態(tài)。

JDK只保證在指定時(shí)間之后被喚醒,不保證準(zhǔn)確的時(shí)間!

注意事項(xiàng):

如果在同步代碼段中執(zhí)行本方法,線程占有的鎖不會(huì)釋放。

這種行為不同于下述的wait()方法。

應(yīng)用實(shí)例:

定期檢測線程T所需的某一資源R是否就緒。

如果線程T檢測到資源R尚未就緒,

則線程T阻塞一段時(shí)間后繼續(xù)循環(huán)檢測。sleep()方法//演示調(diào)用sleep()方法阻塞線程的用法public

classSleepThreadimplementsRunnable{

public

voidrun(){

while(true){ //執(zhí)行線程本身要完成的任務(wù)

... //給其他線程一個(gè)執(zhí)行的機(jī)會(huì)

try{ Thread.sleep(100); }catch(InterruptedExceptionexc){ //本線程的睡眠被另一線程中斷,例如:

//另一線程持有本線程的對(duì)象引用并調(diào)用interrupt()方法

... } } }}join()方法

讓線程進(jìn)入阻塞狀態(tài),等待另一正在運(yùn)行線程結(jié)束后恢復(fù)執(zhí)行。

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

RunningBlocked:調(diào)用另一個(gè)線程的join()方法。

BlockedRunning:取決于join()方法的參數(shù)。

帶時(shí)間參數(shù)版表示有兩個(gè)事件均會(huì)導(dǎo)致線程重返可執(zhí)行狀態(tài):

①另一線程執(zhí)行完畢。

②經(jīng)過的時(shí)間已超過指定的時(shí)間長度(毫秒)。

無時(shí)間參數(shù)版表示僅當(dāng)事件①出現(xiàn)時(shí)線程才重返可執(zhí)行狀態(tài)。

應(yīng)用實(shí)例:

通常結(jié)合isAlive()方法使用。

該方法返回線程是否已啟動(dòng)且未結(jié)束。

設(shè)線程t調(diào)用線程s的join()方法后阻塞,

則當(dāng)s.alive()為false時(shí)線程t重返可執(zhí)行狀態(tài)。

必須放在一個(gè)try-catch塊中處理。

因?yàn)閖oin()造成的阻塞可由interrupt()方法中斷。join()方法classSleeperextendsThread{

publicSleeper(Stringname){

super(name);start(); }

public

voidrun(){

try{ sleep(5000); }catch(InterruptedExceptione){ System.out.println(getName()+"睡眠被中斷。");

return; } System.out.println(getName()+"睡醒了!"); }}publicclassWaiterextendsThread{

privateSleepersleeper;

publicWaiter(Stringname,Sleepersleeper){

super(name);this.sleeper=sleeper;start(); }

public

voidrun(){

try{ sleeper.join(); }catch(InterruptedExceptionexc){} System.out.println(getName()+"完成聯(lián)合。"); }

public

static

voidmain(String[]args){ Sleepers1=newSleeper("S1"); Sleepers2=newSleeper("S2"); Waiterj1=newWaiter("J1",s1); Waiterj2=newWaiter("J2",s2); errupt(); }}S2睡眠被中斷。J2完成聯(lián)合。S1睡醒了!J1完成聯(lián)合。線程間通信的常用技巧是讓一個(gè)線程持有另一線程對(duì)象的引用wait()/notify()/notifyAll()方法

兩個(gè)方法配合使用,使線程進(jìn)入阻塞狀態(tài)后又恢復(fù)為可執(zhí)行狀態(tài)。

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

RunningBlocked:調(diào)用線程占有的鎖的wait()方法。

BlockedRunning:取決于wait()方法的參數(shù)。

帶時(shí)間參數(shù)版表示

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論