《Java程序設(shè)計(jì)教程》課件第十二章:多線程_第1頁
《Java程序設(shè)計(jì)教程》課件第十二章:多線程_第2頁
《Java程序設(shè)計(jì)教程》課件第十二章:多線程_第3頁
《Java程序設(shè)計(jì)教程》課件第十二章:多線程_第4頁
《Java程序設(shè)計(jì)教程》課件第十二章:多線程_第5頁
已閱讀5頁,還剩84頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡介

本章學(xué)習(xí)目標(biāo):●

掌握線程創(chuàng)建的過程●

掌握線程的生命周期●掌了解線程同步機(jī)制以及線程通信●

了解線程的優(yōu)先級(jí)●

掌握線程的同步與死鎖第十二章多線程第1節(jié)part線程概述

線程(Thread)在多任務(wù)處理應(yīng)用程序中起著至關(guān)重要的作用。之前所接觸的應(yīng)用程序都是采用單線程處理模式。單線程在某些功能方面會(huì)受到限制,無法同時(shí)處理多個(gè)互不干擾的任務(wù),只有一個(gè)順序執(zhí)行流;而多線程是同時(shí)有多個(gè)線程并發(fā)執(zhí)行,同時(shí)完成多個(gè)任務(wù),具有多個(gè)順序執(zhí)行流,且執(zhí)行流之間互不干擾。Java語言多多線程提供了非常優(yōu)秀的支持,在程序中可以通過簡便的方式創(chuàng)建多線程。線程概述本節(jié)概述

在操作系統(tǒng)中,每個(gè)獨(dú)立運(yùn)行的程序就是一個(gè)進(jìn)程(Process),當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程。進(jìn)程是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,是具有獨(dú)立功能且處于運(yùn)行過程中的程序。在Windows操作系統(tǒng)中,右擊任務(wù)欄,選擇“啟動(dòng)任務(wù)管理器”菜單命令,可以打開“Windows任務(wù)管理器”窗口,該窗口中的“進(jìn)程”選項(xiàng)卡中顯示系統(tǒng)當(dāng)前正在運(yùn)行的進(jìn)程,如圖12.1所示。12.1.1線程和進(jìn)程線程和進(jìn)程

進(jìn)程具有如下三個(gè)特征:

(1)獨(dú)立性:進(jìn)程是操作系統(tǒng)中獨(dú)立存在的實(shí)體,擁有自己獨(dú)立的資源,每個(gè)進(jìn)行都擁有自己私有的地址空間,其他進(jìn)程不可以直接訪問該地址空間,除非進(jìn)程本身允許的情況下才能進(jìn)行訪問。

(2)動(dòng)態(tài)性:程序只是一個(gè)靜態(tài)的指令集合,只有當(dāng)程序進(jìn)入內(nèi)存運(yùn)行時(shí),才變成一個(gè)進(jìn)程。進(jìn)程是一個(gè)正在內(nèi)存中運(yùn)行的、動(dòng)態(tài)的指令集合,進(jìn)程具有自己的生命周期和各種不同狀態(tài)。

(3)并發(fā)性:多個(gè)進(jìn)程可以在單個(gè)處理器上并發(fā)執(zhí)行,多個(gè)進(jìn)程之間互不影響。12.1.1線程和進(jìn)程

目前的操作系統(tǒng)都支持多線程的并發(fā),但在具體的實(shí)現(xiàn)細(xì)節(jié)上會(huì)采用不同的策略。對(duì)于一個(gè)CPU而言,在某一時(shí)間點(diǎn)只能執(zhí)行一個(gè)進(jìn)程,CPU會(huì)不斷在多個(gè)進(jìn)程之間來回輪換執(zhí)行。并發(fā)性(concurrency)和并行性(parallel)是兩個(gè)相似但又不同的概念:并發(fā)是指多個(gè)事件在同一時(shí)間間隔內(nèi)發(fā)生,其實(shí)質(zhì)是在一個(gè)CPU上同時(shí)運(yùn)行多個(gè)進(jìn)程,CPU要在多個(gè)進(jìn)程之間切換。并發(fā)不是真正的同時(shí)發(fā)生,而是對(duì)有限物理資源進(jìn)行共享以便提高效率。并行是指多個(gè)事件在同一時(shí)刻發(fā)生,其實(shí)質(zhì)是多個(gè)進(jìn)程同一時(shí)刻可在不同的CPU上同時(shí)執(zhí)行,每個(gè)CPU運(yùn)行一個(gè)進(jìn)程。12.1.1線程和進(jìn)程

并發(fā)就像一個(gè)人喂兩個(gè)孩子吃飯,輪換著每人喂一口,表面上兩個(gè)孩子都在吃飯;而并行就是兩個(gè)人喂兩個(gè)孩子吃飯,兩個(gè)孩子也同時(shí)在吃飯。并發(fā)和并行之間的區(qū)別如圖12.2所示。12.1.1線程和進(jìn)程

線程是進(jìn)程的組成部分,一個(gè)線程必須在一個(gè)進(jìn)程之內(nèi),而一個(gè)進(jìn)程可以擁有多個(gè)線程,一個(gè)進(jìn)程中至少有一個(gè)線程。線程是最小的處理單位,線程可以擁有自己的堆棧、計(jì)數(shù)器和局部變量,當(dāng)不能擁有系統(tǒng)資源,多個(gè)線程共享其所在進(jìn)程的系統(tǒng)資源。線程可以完成一定的任務(wù),使用多線程可以在一個(gè)程序中同時(shí)完成多個(gè)任務(wù),在更低的層次中引入多任務(wù)處理。

多線程在多CPU的計(jì)算機(jī)中可以實(shí)現(xiàn)真正物理上的同時(shí)執(zhí)行;而對(duì)于單CPU的計(jì)算機(jī)實(shí)現(xiàn)的只是邏輯上的同時(shí)執(zhí)行,在每個(gè)時(shí)刻,真正執(zhí)行的只有一個(gè)線程,由操作系統(tǒng)進(jìn)行線程管理調(diào)度,但由于CPU的速度很快,讓人感到像是多個(gè)線程在同時(shí)執(zhí)行。12.1.1線程和進(jìn)程

多線程擴(kuò)展了多進(jìn)程的概念,使得同一個(gè)進(jìn)程可以同時(shí)并發(fā)處理多個(gè)任務(wù)。因此,線程也被稱作輕量級(jí)進(jìn)程。多進(jìn)程與多線程是多任務(wù)的兩種類型,兩者之間的主要區(qū)別如下:

(1)進(jìn)程之間的數(shù)據(jù)塊是相互獨(dú)立的,彼此互不影響,進(jìn)程之間需要通過信號(hào)、管道等進(jìn)行交互。

(2)多線程之間的數(shù)據(jù)塊可以共享,一個(gè)進(jìn)程中的多個(gè)線程可以共享程序段、數(shù)據(jù)段等資源。多線程比多進(jìn)程更便于資源共享,同時(shí)Java提供的同步機(jī)制還可以解決線程之間的數(shù)據(jù)完整性問題,使得多線程設(shè)計(jì)更易發(fā)揮作用。多線程編程的優(yōu)點(diǎn)如下:

(1)多線程之間共享內(nèi)存,節(jié)約系統(tǒng)資源成本;

(2)充分利用CPU,執(zhí)行并發(fā)任務(wù)效率高;

(3)Java內(nèi)置多線程功能支持,簡化編程模型

(4)GUI應(yīng)用通過啟動(dòng)單獨(dú)線程收集用戶界面事件,簡化異步事件處理,使GUI界面的交互性更好。12.1.1線程和進(jìn)程Java線程模型提供線程所必需的功能支持,基本的Java線程模型有Thread類、Runnable接口、Callable接口和Future接口等,這些線程模型都是面向?qū)ο蟮?。Thread類將線程所必需的功能進(jìn)行封裝,其常用的方法如表12-1所示。12.1.2Java線程模型Java線程模型Thread類的run()方法是線程中最重要的方法,該方法用于執(zhí)行線程要完成的任務(wù);當(dāng)創(chuàng)建一個(gè)線程時(shí),要完成自己的任務(wù),則需要重寫run()方法。此外,Thread類還提供了start()方法,該方法用于負(fù)責(zé)線程的啟動(dòng);當(dāng)調(diào)用start()方法成功地啟動(dòng)線程后,系統(tǒng)會(huì)自動(dòng)調(diào)用Thread類的run()方法來執(zhí)行線程。因此,任何繼承Thread類的線程都可以通過start()方法來啟動(dòng)。Runnable接口用于標(biāo)識(shí)某個(gè)Java類可否作為線程類,該接口只有一個(gè)抽象方法run(),即線程中最重要的執(zhí)行體,用于執(zhí)行線程中所要完成的任務(wù)。Runnable接口定義在java.lang包中,定義代碼如下所示。packagejava.lang;publicinterfaceRunnable{ publicabstractvoidrun();}12.1.2Java線程模型Callable接口是Java5新增的接口,該接口中提供一個(gè)call()方法作為線程的執(zhí)行體。call()方法比run()方法功能更強(qiáng)大,call()方法可以有返回值,也可以聲明拋出異常。Callable接口定義在java.util.concurrent包中,定義代碼如下所示。packagejava.util.concurrent;publicinterfaceCallable<V>{ Vcall()throwsException;}12.1.2Java線程模型Future接口用來接收Callable接口中call()方法的返回值。Future接口提供一些方法用于控制與其關(guān)聯(lián)的Callable任務(wù)。Future接口提供的方法如表12-2所示。12.1.2Java線程模型Callable接口有泛型限制,該接口中的泛型形參類型與call()方法返回值的類型相同;而且Callable接口是函數(shù)式接口,因此從Java8開始可以使用Lambda表達(dá)式創(chuàng)建Callable對(duì)象。

每個(gè)能夠獨(dú)立運(yùn)行的程序就是一個(gè)進(jìn)程,每個(gè)進(jìn)程至少包含一個(gè)線程,即主線程。在Java語言中,每個(gè)能夠獨(dú)立運(yùn)行的Java程序都至少有一個(gè)主線程,且在程序啟動(dòng)時(shí),JVM會(huì)自動(dòng)創(chuàng)建一個(gè)主線程來執(zhí)行該程序中的main()方法。因此,主線程有以下兩個(gè)特點(diǎn):

(1)一個(gè)進(jìn)程肯定包含一個(gè)主線程

(2)主線程用來執(zhí)行main()方法

下述程序在main()方法中,調(diào)用Thread類的靜態(tài)方法currentThread()來獲取主線程,代碼如下所示。12.1.3主線程主線程【代碼12.1】MainThreadExample.javapackagecom;publicclassMainThreadExample{ publicstaticvoidmain(String[]args){ //調(diào)用Thread類的currentThread()獲取當(dāng)前線程 Threadt=Thread.currentThread(); //設(shè)置線程名 t.setName("MyThread"); System.out.println("主線程是:"+t); System.out.println("線程名:"+t.getName()); System.out.println("線程ID:"+t.getId()); }}12.1.3主線程

上述代碼中,通過Thread.currentThread()靜態(tài)方法來獲取當(dāng)前線程對(duì)象,由于是在main()方法中,所以獲取的線程是主線程。調(diào)用setName()方法可以設(shè)置線程名,調(diào)用getId()方法可以獲取線程的Id號(hào),調(diào)用getName()方法可以獲取線程的名字。

程序運(yùn)行結(jié)果如下:

主線程是:Thread[MyThread,5,main]

線程名:MyThread

線程ID:112.1.3主線程第2節(jié)part線程的創(chuàng)建和啟動(dòng)

基于Java線程模型,創(chuàng)建線程的方式有三種:

(1)第一種方式是繼承Thread類,重寫Thread類中的run()方法,直接創(chuàng)建線程。

(2)第二種方式是實(shí)現(xiàn)Runnable接口,再通過Thread類和Runnable的實(shí)現(xiàn)類間接創(chuàng)建一個(gè)線程。

(3)第三種方式是使用Callable接口或Future接口間接創(chuàng)建線程。

上述三種方式從本質(zhì)上是一致的,最終都是通過Thread類來建立線程。提供Runnable、Callable和Future接口模型是由于Java不支持多繼承,如果一個(gè)線程類繼承了Thread類,則不能再繼承其他的類,因此可以通過實(shí)現(xiàn)接口的方式間接創(chuàng)建線程。

采用Runnable、Callable和Future接口的方式創(chuàng)建線程時(shí),線程類還可以繼承其他類,且多個(gè)線程之間可以共享一個(gè)target目標(biāo)對(duì)象,適合多個(gè)相同線程處理同一個(gè)資源的情況,從而可以將CPU、代碼和數(shù)據(jù)分開,形成清晰的數(shù)據(jù)模型。線程的啟動(dòng)和創(chuàng)建本節(jié)概述12.2.1繼承Thread類

通過繼承Thread類來創(chuàng)建并啟動(dòng)線程的步驟如下:

(1)定義一個(gè)子類繼承Thread類,并重寫run()方法。

(2)創(chuàng)建子類的實(shí)例,即實(shí)例化線程對(duì)象。

(3)調(diào)用線程對(duì)象的start()方法啟動(dòng)該線程。Thread類的start()方法將調(diào)用run()方法,該方法用于啟動(dòng)線程并運(yùn)行。因此start()方法不能多次調(diào)用,當(dāng)多次調(diào)用td.start()方法時(shí)會(huì)拋出一個(gè)IllegalThreadStateException異常。下述案例示例通過繼承Thread類來創(chuàng)建并啟動(dòng)線程的步驟,代碼如下所示。繼承Thread類繼承Thread類【代碼12.2】ThreadExample.javapackagecom;//繼承Thread類publicclassThreadExampleextendsThread{ //重寫run()方法 publicvoidrun(){ for(inti=0;i<10;i++){ //繼承Thread類時(shí),直接使用this即可獲取當(dāng)前線程對(duì)象 //調(diào)用getName()方法返回當(dāng)前線程的名字 System.out.println(this.getName()+":"+i); } }12.2.1繼承Thread類 publicstaticvoidmain(String[]args){ //創(chuàng)建線程對(duì)象 ThreadExampletd=newThreadExample(); //調(diào)用start()方法啟動(dòng)線程 td.start(); //主線程任務(wù) for(inti=1100;i<1110;i++) //使用Thread.currentThread().getName()獲取主線程名字 System.out.println(Thread.currentThread().getName()+":"+i); }}12.2.1繼承Thread類

因?yàn)榫€程在CPU中的執(zhí)行是由操作系統(tǒng)所控制,執(zhí)行次序是不確定的,除非使用同步機(jī)制強(qiáng)制按特定的順序執(zhí)行,所以程序代碼運(yùn)行的結(jié)果會(huì)因調(diào)度次序不同而不同。程序執(zhí)行結(jié)果可能如下:main:1101Thread-0:1main:1102Thread-0:2main:1103……..

在創(chuàng)建td線程對(duì)象時(shí)并未指定該線程的名字,因此所輸出的線程名是系統(tǒng)的默認(rèn)值“Thread-0”。對(duì)于輸出結(jié)果,不同機(jī)器所執(zhí)行的結(jié)果可能不同,在同一機(jī)器上多次運(yùn)行同一個(gè)程序也可能生成不同結(jié)果。12.2.112.2.2實(shí)現(xiàn)Runable接口

創(chuàng)建線程的第二種方式是實(shí)現(xiàn)Runnable接口。Runnable接口中只有一個(gè)run()方法,一個(gè)類實(shí)現(xiàn)Runnable接口后,并不代表該類是個(gè)“線程”類,不能直接啟動(dòng)線程,必須通過Thread類的實(shí)例來創(chuàng)建并啟動(dòng)線程。通過Runnable接口創(chuàng)建并啟動(dòng)線程的步驟如下:

(1)定義一個(gè)類實(shí)現(xiàn)Runnable接口,并實(shí)現(xiàn)該接口中的run()方法;

(2)創(chuàng)建一個(gè)Thread類的實(shí)例,將Runnable接口的實(shí)現(xiàn)類所創(chuàng)建的對(duì)象作為參數(shù)傳入Thread類的構(gòu)造方法中;

(3)調(diào)用Thread對(duì)象的start()方法啟動(dòng)該線程。

下述案例示例通過實(shí)現(xiàn)Runnable接口創(chuàng)建并啟動(dòng)線程的步驟,代碼如下所示。實(shí)現(xiàn)Runable接口實(shí)現(xiàn)Runable接口【代碼12.3】RunnableExamble.javapackagecom;//實(shí)現(xiàn)Runnable接口publicclassRunnableExambleimplementsRunnable{ //重寫run()方法 publicvoidrun(){ //獲取當(dāng)前線程的名字 for(inti=0;i<10;i++) //實(shí)現(xiàn)Runnable接口時(shí),只能使用Thread.currentThread()獲取當(dāng)前線程對(duì)象 //再調(diào)用getName()方法返回當(dāng)前線程的名字 System.out.println(Thread.currentThread().getName()+":"+i); }12.2.2實(shí)現(xiàn)Runable接口 publicstaticvoidmain(String[]args){ //創(chuàng)建一個(gè)Thread類的實(shí)例,其參數(shù)是RunnableExamble類的對(duì)象 Threadtd=newThread(newRunnableExamble()); //調(diào)用start()方法啟動(dòng)線程 td.start(); //主線程任務(wù) for(inti=1100;i<1110;i++) //使用Thread.currentThread().getName()獲取主線程名字 System.out.println(Thread.currentThread().getName()+":"+i); }}12.2.2實(shí)現(xiàn)Runable接口

上述代碼定義了一個(gè)RunnableExamble類,該類實(shí)現(xiàn)了Runnable接口,并實(shí)現(xiàn)run()方法,這樣的類可以稱為線程任務(wù)類。直接調(diào)用Thread類或Runnable接口所創(chuàng)建的對(duì)象的run()方法是無法啟動(dòng)線程的,必須通過Thread的start()方法才能啟動(dòng)線程。程序執(zhí)行的結(jié)果可能如下:main:1100Thread-0:0Thread-0:1Thread-0:2……..12.2.212.2.3使用Callable和Future接口

創(chuàng)建線程的第三種方式是使用Callable和Future接口。Callable接口提供一個(gè)call()方法作為線程的執(zhí)行體,該方法的返回值使用Future接口來代表。從Java5開始,為Future接口提供一個(gè)FutureTask實(shí)現(xiàn)類,該類同時(shí)實(shí)現(xiàn)了Future和Runnable兩個(gè)接口,因此可以作為Thread類的target參數(shù)。使用Callable和Future接口的最大優(yōu)勢在于可以在線程執(zhí)行完成之后獲得執(zhí)行結(jié)果。

使用Callable和Future接口創(chuàng)建并啟動(dòng)線程的步驟如下:

(1)創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法,該方法將作為線程的執(zhí)行體,并具有返回值;然后創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例。

(2)使用FutureTask類來包裝Callable對(duì)象,在FutureTask對(duì)象中封裝了Callable對(duì)象的call()方法的返回值。

(3)使用FutureTask對(duì)象作為Thread對(duì)象的target,創(chuàng)建并啟動(dòng)新線程。

(4)調(diào)用FutureTask對(duì)象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值。

下述案例示例通過Callable和Future接口創(chuàng)建并啟動(dòng)線程的步驟,代碼如下所示。使用Callable和Future接口使用Callable和Future接口【代碼12.4】CallableFutureExample.javapackagecom;importjava.util.concurrent.Callable;importjava.util.concurrent.FutureTask;//創(chuàng)建Callable接口的實(shí)現(xiàn)類classTaskimplementsCallable<Integer>{ //實(shí)現(xiàn)call()方法,作為線程執(zhí)行體 publicIntegercall()throwsException{ inti=0; for(;i<10;i++) System.out.println(Thread.currentThread().getName()+":"+i); //call()方法可以有返回值 returni; }}12.2.3使用Callable和Future接口publicclassCallableFutureExample{ publicstaticvoidmain(String[]args){ //使用FutureTask類包裝Callable實(shí)現(xiàn)類的實(shí)例 FutureTask<Integer>task=newFutureTask<>(newTask()); //創(chuàng)建線程,使用FutureTask對(duì)象task作為Thread對(duì)象的target, //并調(diào)用start()方法啟動(dòng)線程 Threadtd=newThread(task,"子線程"); td.start(); //調(diào)用FutureTask對(duì)象task的get()方法獲取子線程執(zhí)行結(jié)束后的返回值 try{ System.out.println("子線程返回值:"+task.get()); }catch(Exceptione){ e.printStackTrace(); } //主線程任務(wù) for(inti=1100;i<1110;i++) //使用Thread.currentThread().getName()獲取主線程名字 System.out.println(Thread.currentThread().getName()+":"+i); }}12.2.3使用Callable和Future接口

上述代碼先定義一個(gè)Task類,該類實(shí)現(xiàn)Callable接口并重寫call()方法,call()的返回值為整型,因此Callable接口中對(duì)應(yīng)的泛型限制為Integer,即Callable<Integer>。在main()方法中,先創(chuàng)建FutureTask<Integer>類的對(duì)象task,該對(duì)象包裝Task類;再創(chuàng)建Thread對(duì)象并啟動(dòng)線程;最后調(diào)用FutureTask對(duì)象task的get()方法獲取子線程執(zhí)行結(jié)束后的返回值。整個(gè)程序所實(shí)現(xiàn)的功能與前兩種方式一樣,只是增加了子線程返回值。

程序運(yùn)行結(jié)果如下:

子線程:0

子線程:1

子線程:2……..

子線程返回值:10main:1100main:1101……12.2.3使用Callable和Future接口

從Java8開始,可以直接使用Lambda表達(dá)式創(chuàng)建Callable對(duì)象,下述案例示例通過Lambda表達(dá)式創(chuàng)建Callable對(duì)象,代碼如下所示。【代碼12.5】LambdaCallableExample.javapackagecom;importjava.util.concurrent.Callable;importjava.util.concurrent.FutureTask;publicclassLambdaCallableExample{publicstaticvoidmain(String[]args){ //使用Lambda表達(dá)式創(chuàng)建Callable<Integer>對(duì)象 //使用FutureTask類包裝Callable對(duì)象 FutureTask<Integer>task=newFutureTask<>(

(Callable<Integer>)()->{ inti=0; for(;i<10;i++) System.out.println(Thread.currentThread().getName()+":"+i); //call()方法可以有返回值 returni; });12.2.3使用Callable和Future接口 //創(chuàng)建線程,使用FutureTask對(duì)象task作為Thread對(duì)象的target, //并調(diào)用start()方法啟動(dòng)線程 Threadtd=newThread(task,"子線程"); td.start(); //調(diào)用FutureTask對(duì)象task的get()方法獲取子線程執(zhí)行結(jié)束后的返回值 try{ System.out.println("子線程返回值:"+task.get()); }catch(Exceptione){ e.printStackTrace(); } //主線程任務(wù) for(inti=1100;i<1110;i++) //使用Thread.currentThread().getName()獲取主線程名字 System.out.println(Thread.currentThread().getName()+":"+i);}}

上述代碼加粗部分就是Lambda表達(dá)式,可以直接使用Lambda表達(dá)式創(chuàng)建Callable對(duì)象,而無須先創(chuàng)建Callable實(shí)現(xiàn)類,但Lambda表達(dá)式必須在jdk1.8版本后才可以運(yùn)行。

在JavaAPI中,定義的FutureTask類實(shí)際上直接實(shí)現(xiàn)RunnableFuture接口,而RunnableFuture接口繼承Runnable和Future兩個(gè)接口,因此FutureTask類即實(shí)現(xiàn)了Runnable接口,又實(shí)現(xiàn)了Future接口。12.2.3第3節(jié)part線程的生命周期

線程具有生命周期,當(dāng)線程被創(chuàng)建并啟動(dòng)后,不會(huì)立即進(jìn)入執(zhí)行狀態(tài),也不會(huì)一直處于執(zhí)行狀態(tài)。在線程的生命周期中,要經(jīng)過5種狀態(tài):新建(New)、就緒(Runnable)、運(yùn)行(Running)、阻塞(Blocked)和死亡(Dead)。線程狀態(tài)之間的轉(zhuǎn)換如圖12-3所示。線程的生命周期本節(jié)介紹12.3.1新建和就緒狀態(tài)

當(dāng)程序使用new關(guān)鍵字創(chuàng)建一個(gè)線程之后,該線程就處于新建狀態(tài),此時(shí)與其他Java對(duì)象一樣,僅由JVM為其分配內(nèi)存并初始化。新建狀態(tài)的線程沒有表現(xiàn)出任何動(dòng)態(tài)特征,程序也不會(huì)執(zhí)行線程的執(zhí)行體。當(dāng)線程對(duì)象調(diào)用start()方法之后,線程就處于就緒狀態(tài),相當(dāng)于“等待執(zhí)行”。此時(shí),調(diào)度程序就可以把CPU分配給該線程,JVM會(huì)為線程創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器。處于就緒狀態(tài)的線程并沒有開始運(yùn)行,只是表示該線程準(zhǔn)備就緒等待執(zhí)行。

注意只能對(duì)新建狀態(tài)的線程調(diào)用start()方法,即new完一個(gè)線程后,只能調(diào)用一次start()方法,否則將引發(fā)IllegalThreadStateException異常。

下述案例示例了新建線程重復(fù)調(diào)用start()方法引發(fā)異常,代碼如下所示。新建和就緒狀態(tài)新建和就緒狀態(tài)【代碼12.6】IllegalThreadExample.javapackagecom;publicclassIllegalThreadExample{ publicstaticvoidmain(String[]args){ //創(chuàng)建線程 Threadt=newThread(newRunnable(){ publicvoidrun(){ for(inti=0;i<10;i++) System.out.print(i+""); } }); t.start(); t.start(); }}

上述代碼三次調(diào)用start()方法,多次啟動(dòng)線程,因此會(huì)引發(fā)IllegalThreadStateException異常。運(yùn)行結(jié)果可能如下所示:Exceptioninthread"main"java.lang.IllegalThreadStateException0123456789 atjava.lang.Thread.start(UnknownSource) atcom.IllegalThreadExample.main(IllegalThreadExample.java:12)12.3.112.3.2運(yùn)行和阻塞狀態(tài)

處于就緒狀態(tài)的線程獲得CPU后,開始執(zhí)行run()方法的線程執(zhí)行體,此時(shí)該線程處于運(yùn)行狀態(tài)。如果計(jì)算機(jī)的CPU是單核的,則在任何時(shí)刻只有一個(gè)線程處于運(yùn)行狀態(tài)。一個(gè)線程開始運(yùn)行后,不可能一直處于運(yùn)行狀態(tài)。線程在運(yùn)行過程中需要被中斷,目的是使其他線程獲得執(zhí)行的機(jī)會(huì),線程調(diào)度的細(xì)節(jié)取決于底層平臺(tái)所采用的策略。

目前UNIX系統(tǒng)采用的是時(shí)間片算法策略,Windows系統(tǒng)采用的則是搶占式策略,另外一種小型設(shè)備(手機(jī))則可能采用協(xié)作式調(diào)度策略。對(duì)于采用搶占式策略的系統(tǒng)而言,系統(tǒng)會(huì)給每個(gè)可執(zhí)行的線程一個(gè)小時(shí)間段來處理任務(wù);當(dāng)該時(shí)間段用完后,系統(tǒng)就會(huì)剝奪該線程所占用的資源,讓其他線程獲得執(zhí)行的機(jī)會(huì)。在選擇下一個(gè)線程時(shí),系統(tǒng)會(huì)考慮線程的優(yōu)先級(jí)。運(yùn)行和阻塞狀態(tài)運(yùn)行和阻塞狀態(tài)當(dāng)線程出現(xiàn)以下情況時(shí),會(huì)進(jìn)入阻塞狀態(tài):(1)調(diào)用sleep()方法,主動(dòng)放棄所占用的處理器資源;(2)調(diào)用了一個(gè)阻塞式IO方法,在該方法返回之前,該線程被阻塞;(3)線程試圖獲得一個(gè)同步監(jiān)視器,但該同步監(jiān)視器正被其他線程所持有;(4)執(zhí)行條件還未滿足,調(diào)用wait()方法使線程進(jìn)入等待狀態(tài),等待其他線程的通知;(5)程序調(diào)用了線程的suspend()方法將該線程掛起,但該方法容易導(dǎo)致死鎖,因此應(yīng)該盡量避免使用。12.3.2運(yùn)行和阻塞狀態(tài)

正在執(zhí)行的線程被阻塞之后,其他線程就可以獲得執(zhí)行的機(jī)會(huì),被阻塞的線程會(huì)在合適的時(shí)機(jī)重新進(jìn)入就緒狀態(tài),等待線程調(diào)度器再次調(diào)度。

當(dāng)線程出現(xiàn)如下幾種情況時(shí),線程可以解除阻塞進(jìn)入就緒狀態(tài):

(1)調(diào)用sleep()方法的線程經(jīng)過了指定的時(shí)間;

(2)線程調(diào)用的阻塞式IO方法以經(jīng)返回;

(3)線程成功地獲得了同步監(jiān)視器;

(4)線程處于等待狀態(tài),其他線程調(diào)用notify()或notifyAll()方法發(fā)出了一個(gè)通知時(shí),則線程回到就緒狀態(tài);

(5)處于掛起狀態(tài)的線程被調(diào)用了resume()恢復(fù)方法。12.3.2運(yùn)行和阻塞狀態(tài)

在線程運(yùn)行的過程中,可以通過sleep()方法使線程暫時(shí)停止運(yùn)行,進(jìn)入休眠狀態(tài)。在使用sleep()方法時(shí)需要注意以下兩點(diǎn):

(1)sleep()方法的參數(shù)是以毫秒為基本單位,例如sleep(2000)則休眠2秒鐘;

(2)sleep()方法聲明了InterruptedException異常,因此調(diào)用sleep()方法時(shí)要么放在try…catch語句中捕獲該異常并處理,要么在方法后使用throws顯式聲明拋出該異常。

可以通過Thread類的isAlive()方法來判斷線程是否處于運(yùn)行狀態(tài)。當(dāng)線程處于就緒、運(yùn)行和阻塞三種狀態(tài)時(shí),isAlive()方法的返回值為true;當(dāng)線程處于新建、死亡兩種狀態(tài)時(shí),isAlive()方法的返回值為false。

下述案例示例了線程的創(chuàng)建、運(yùn)行和死亡三個(gè)狀態(tài),代碼如下所示。12.3.2運(yùn)行和阻塞狀態(tài)【代碼12.7】ThreadLifeExample.javapackagecom;publicclassThreadLifeExampleextendsThread{ publicvoidrun(){ intsum=0; for(inti=0;i<=100;i++) sum+=i; System.out.println("sum="+sum); } publicstaticvoidmain(String[]args)throwsInterruptedException{ ThreadLifeExampletle=newThreadLifeExample(); System.out.println("新建狀態(tài)isAlive():"+tle.isAlive()); tle.start(); System.out.println("運(yùn)行狀態(tài)isAlive():"+tle.isAlive()); Thread.sleep(2000); System.out.println("線程結(jié)束isAlive():"+tle.isAlive()); }}12.3.2運(yùn)行和阻塞狀態(tài)

程序運(yùn)行結(jié)果如下:

新建狀態(tài)isAlive():false

運(yùn)行狀態(tài)isAlive():truesum=5050

線程結(jié)束isAlive():false

注意:線程調(diào)用wait()方法進(jìn)入等待狀態(tài)后,需其他線程調(diào)用notify()或notifyAll()方法發(fā)出通知才能進(jìn)入就緒狀態(tài)。使用suspend()和resume()方法可以掛起和喚醒線程,但這兩個(gè)方法可能會(huì)導(dǎo)致不安全因素。如果對(duì)某個(gè)線程調(diào)用interrupt()方法發(fā)出中斷請(qǐng)求,則該線程會(huì)根據(jù)線程狀態(tài)拋出InterruptedException異常,對(duì)異常進(jìn)行處理時(shí)可以再次調(diào)度該線程。12.3.2死亡狀態(tài)線程結(jié)束后就處于死亡狀態(tài),結(jié)束線程有以下三種方式:(1)線程執(zhí)行完成run()或call()方法,線程正常結(jié)束;(2)線程拋出一個(gè)未捕獲的Exception或Error;(3)調(diào)用stop()方法直接停止線程,該方法容易導(dǎo)致死鎖,通常不推薦使用。死亡狀態(tài)12.3.2死亡狀態(tài)

主線程結(jié)束時(shí),其他子線程不受任何影響,并不會(huì)隨主線程的結(jié)束而結(jié)束。一旦子線程啟動(dòng)起來,子線程就擁有和主線程相同的地位,子線程不會(huì)受主線程的影響。

為了測試某個(gè)線程是否死亡,可以通過線程對(duì)象的isAlive()方法來獲得線程狀態(tài),當(dāng)方法返回值為false時(shí),線程處于死亡或新建狀態(tài)。不要試圖對(duì)一個(gè)已經(jīng)死亡的線程調(diào)用start()方法使其重新啟動(dòng),線程死亡就是死亡,該線程不可再次作為線程執(zhí)行。Thread類中的join()方法可以讓一個(gè)線程等待另一個(gè)線程完成后,繼續(xù)執(zhí)行原線程中的任務(wù)。當(dāng)在某個(gè)程序執(zhí)行流中調(diào)用其他線程的join()方法時(shí),當(dāng)前線程將被阻塞,直到另一個(gè)線程執(zhí)行完為止。join()方法通常由使用線程的程序調(diào)用,當(dāng)其他線程都執(zhí)行結(jié)束后,再調(diào)用主線程進(jìn)一步操作。

下述案例示例了join()方法的使用,代碼如下所示。12.3.2死亡狀態(tài)【代碼12.8】JoinExample.javapackagecom;classJoinThreadextendsThread{ publicJoinThread(){ super(); } publicJoinThread(Stringstr){ super(str); } publicvoidrun(){ for(inti=0;i<10;i++) System.out.println(this.getName()+":"+i); }}12.3.2死亡狀態(tài)publicclassJoinExample{ publicstaticvoidmain(String[]args){ //創(chuàng)建子線程 JoinThreadt1=newJoinThread("被Join的子線程"); //啟動(dòng)子線程 t1.start(); //等待子線程執(zhí)行完畢 try{ t1.join(); }catch(InterruptedExceptione){ //TODOAuto-generatedcatchblock e.printStackTrace(); } //輸出主線程名 System.out.println("主線程名為:"+Thread.currentThread().getName()); //子線程已經(jīng)處于死亡狀態(tài),其isAlive()方法返回值為false System.out.println("子線程死亡狀態(tài)isAlive():"+t1.isAlive()); //再次啟動(dòng)子線程,拋出異常 t1.start(); }}12.3.2死亡狀態(tài)

上述代碼中開始調(diào)用了線程的join()方法,最后又對(duì)死亡狀態(tài)的線程再次調(diào)用start()方法,運(yùn)行結(jié)果如下所示:…..

被Join的子線程:8

被Join的子線程:9

主線程名為:main

子線程死亡狀態(tài)isAlive():falseExceptioninthread"main"java.lang.IllegalThreadStateException atjava.lang.Thread.start(UnknownSource) atcom.JoinExample.main(JoinExample.java:33)

在上述代碼中,注銷掉join()方法的調(diào)用和對(duì)死亡狀態(tài)線程的start()方法的再次調(diào)用,運(yùn)行結(jié)果可能如下:

主線程名為:main

被Join的子線程:0

被Join的子線程:1

子線程死亡狀態(tài)isAlive():true

被Join的子線程:2

被Join的子線程:3……12.3.2第4節(jié)part線程的優(yōu)先級(jí)

每個(gè)線程執(zhí)行時(shí)都具有一定的優(yōu)先級(jí),線程的優(yōu)先級(jí)代表該線程的重要程度。當(dāng)有多個(gè)線程同時(shí)處于可執(zhí)行狀態(tài)并等待獲得CPU處理器時(shí),系統(tǒng)將根據(jù)各個(gè)線程的優(yōu)先級(jí)來調(diào)度各線程,優(yōu)先級(jí)越高的線程獲得CPU時(shí)間的機(jī)會(huì)越多,而優(yōu)先級(jí)低的線程則獲得較少的執(zhí)行機(jī)會(huì)。

每個(gè)線程都有默認(rèn)的優(yōu)先級(jí),其優(yōu)先級(jí)都與創(chuàng)建該線程的父線程的優(yōu)先級(jí)相同。在默認(rèn)情況下,主線程具有普通優(yōu)先級(jí),由主線程創(chuàng)建的子線程也具有普通優(yōu)先級(jí)。Thread類提供三個(gè)靜態(tài)常量來標(biāo)識(shí)線程的優(yōu)先級(jí):

(1)MAX_PRIORITY:最高優(yōu)先級(jí),其值為10;

(2)NORM_PRIORITY:普通優(yōu)先級(jí),其值為5;

(3)MIN_PRIORITY:最低優(yōu)先級(jí),其值為1。線程的優(yōu)先級(jí)線程的優(yōu)先級(jí)Thread類提供了setPriority()方法來對(duì)線程的優(yōu)先級(jí)進(jìn)行設(shè)置,而getPriority()方法來獲取線程的優(yōu)先級(jí)。setPriority()方法的參數(shù)是一個(gè)整數(shù)(1~10),也可以使用Thread類提供的三個(gè)優(yōu)先級(jí)靜態(tài)常量。

線程的優(yōu)先級(jí)高度依賴于操作系統(tǒng),并不是所有的操作系統(tǒng)都支持Java的10個(gè)優(yōu)先級(jí),例如Windows2000僅提供7個(gè)優(yōu)先級(jí)。因此,盡量避免直接使用整數(shù)給線程指定優(yōu)先級(jí),提倡使用MAX_PRIORITY、NORM_PRIORITY和MIN_PRIORITY三個(gè)優(yōu)先級(jí)靜態(tài)常量。另外,優(yōu)先級(jí)并不能保證線程的執(zhí)行次序,因此應(yīng)避免使用線程優(yōu)先級(jí)作為構(gòu)建任務(wù)執(zhí)行順序的標(biāo)準(zhǔn)。

下述案例示例了線程優(yōu)先級(jí)的設(shè)置及使用,代碼如下所示。線程的優(yōu)先級(jí)【代碼12.9】PriorityExample.javapackagecom;classMyPriorityThreadextendsThread{ publicMyPriorityThread(){ super(); } publicMyPriorityThread(Stringname){ super(name); } publicvoidrun(){ for(inti=0;i<10;i++){ System.out.println(this.getName()+",其優(yōu)先級(jí)是:"+this.getPriority() +",循環(huán)變量的值為:"+i); } }}線程的優(yōu)先級(jí)publicclassPriorityExample{ publicstaticvoidmain(String[]args){ //輸出主線程的優(yōu)先級(jí) System.out.println("主線程的優(yōu)先級(jí):"+Thread.currentThread().getPriority()); //創(chuàng)建子線程,并設(shè)置不同優(yōu)先級(jí) MyPriorityThreadt1=newMyPriorityThread("高級(jí)"); t1.setPriority(Thread.MAX_PRIORITY); MyPriorityThreadt2=newMyPriorityThread("普通"); t2.setPriority(Thread.NORM_PRIORITY); MyPriorityThreadt3=newMyPriorityThread("低級(jí)"); t3.setPriority(Thread.MIN_PRIORITY); MyPriorityThreadt4=newMyPriorityThread("指定值級(jí)"); t4.setPriority(4); //啟動(dòng)所有子線程 t1.start(); t2.start(); t3.start(); t4.start(); }}線程的優(yōu)先級(jí)程序運(yùn)行執(zhí)行結(jié)果可能如下:主線程的優(yōu)先級(jí):5普通,其優(yōu)先級(jí)是:5,循環(huán)變量的值為:0高級(jí),其優(yōu)先級(jí)是:10,循環(huán)變量的值為:0高級(jí),其優(yōu)先級(jí)是:10,循環(huán)變量的值為:1高級(jí),其優(yōu)先級(jí)是:10,循環(huán)變量的值為:2高級(jí),其優(yōu)先級(jí)是:10,循環(huán)變量的值為:3普通,其優(yōu)先級(jí)是:5,循環(huán)變量的值為:1高級(jí),其優(yōu)先級(jí)是:10,循環(huán)變量的值為:4指定值,其優(yōu)先級(jí)是:4,循環(huán)變量的值為:0…….通過運(yùn)行結(jié)果可以看出,優(yōu)先級(jí)越高的線程提前獲得執(zhí)行機(jī)會(huì)就越多。

線程的優(yōu)先級(jí)第5節(jié)part線程的同步

多線程訪問同一資源數(shù)據(jù)時(shí),很容易出現(xiàn)線程安全問題。以多窗口出售車票為例,一旦多線程并發(fā)訪問,就可能出現(xiàn)問題,造成一票多售的現(xiàn)象。在Java中,提供了線程同步的概念以保證某個(gè)資源在某一時(shí)刻只能由一個(gè)線程訪問,以此保證共享數(shù)據(jù)的一致性。Java使用監(jiān)控器(也稱對(duì)象鎖)實(shí)現(xiàn)同步。每個(gè)對(duì)象都有一個(gè)監(jiān)控器,使用監(jiān)控器可以保證一次只允許一個(gè)線程執(zhí)行對(duì)象的同步語句。即在對(duì)象的同步語句執(zhí)行完畢前,其他試圖執(zhí)行當(dāng)前對(duì)象的同步語句的線程都將處于阻塞狀態(tài),只有線程在當(dāng)前對(duì)象的同步語句執(zhí)行完畢后,監(jiān)控器才會(huì)釋放對(duì)象鎖,并讓優(yōu)先級(jí)最高的阻塞線程處理同步語句。

線程同步通常采用三種方式:同步代碼塊、同步方法和同步鎖。線程的同步本節(jié)介紹12.5.1同步代碼塊

使用同步代碼塊實(shí)現(xiàn)同步功能,只需將對(duì)實(shí)例的訪問語句放入一個(gè)同步塊中,其語法格式如下:synchronized(object){ //需要同步的代碼塊}

其中:synchronized是同步關(guān)鍵字;object是同步監(jiān)視器,其數(shù)據(jù)類型不能是基本數(shù)據(jù)類型。線程開始執(zhí)行同步代碼之前,必須先獲得同步監(jiān)視器的鎖定,并且,任何時(shí)刻只能有一個(gè)線程可以獲得對(duì)同步監(jiān)視器的鎖定,當(dāng)同步代碼塊執(zhí)行完成后,該線程會(huì)釋放對(duì)該同步監(jiān)視器的鎖定。

下述案例示例了同步代碼塊的聲明和使用,代碼如下所示。同步代碼塊同步代碼塊【代碼12.10】SynBlockExample.javapackagecom;//銀行帳戶類classBankAccount{ //銀行賬號(hào) privateStringbankNo; //銀行余額 privatedoublebalance; //構(gòu)造方法 publicBankAccount(StringbankNo,doublebalance){ this.bankNo=bankNo; this.balance=balance; } publicStringgetBankNo(){ returnbankNo; } publicvoidsetBankNo(StringbankNo){ this.bankNo=bankNo; } publicdoublegetBalance(){ returnbalance; } publicvoidsetBalance(doublebalance){ this.balance=balance; }}12.5.1同步代碼塊

publicclassSynBlockExampleextendsThread{ //銀行賬戶 privateBankAccountaccount; //操作金額,正數(shù)為存錢,負(fù)數(shù)為取錢 privatedoublemoney; publicSynBlockExample(Stringname,BankAccountaccount,doublemoney){ super(name); this.account=account; this.money=money; } //線程任務(wù) publicvoidrun(){ synchronized(this.account){ //獲取目賬戶的金額 doubled=this.account.getBalance(); //如果操作的金額money<0,則代表取錢操作,//同時(shí)判斷賬戶金額是否低于取錢金額 if(money<0&&d<-money){ System.out.println(this.getName()+"操作失敗,余額不足!"); return; }else{12.5.1同步代碼塊 //對(duì)賬戶金額進(jìn)行操作 d+=money; System.out.println(this.getName()+"操作成功,目前賬戶余額為:"+d); try{ //休眠10毫秒 Thread.sleep(10); }catch(InterruptedExceptione){ e.printStackTrace(); } //修改賬戶金額 this.account.setBalance(d);

} } }

publicstaticvoidmain(String[]args){ //創(chuàng)建一個(gè)銀行賬戶實(shí)例 BankAccountmyAccount=newBankAccount("101",5000); 12.5.1同步代碼塊 //創(chuàng)建多個(gè)線程,對(duì)賬戶進(jìn)行存取錢操作 SynBlockExamplet1=newSynBlockExample("T1",myAccount,-3000); SynBlockExamplet2=newSynBlockExample("T2",myAccount,-3000); SynBlockExamplet3=newSynBlockExample("T3",myAccount,1000); //啟動(dòng)線程 t1.start(); t2.start(); t3.start(); //等待所有子線程完成 try{ t1.join(); t2.join(); t3.join(); }catch(InterruptedExceptione){ e.printStackTrace(); } //輸出賬戶信息 System.out.println("賬號(hào):"+myAccount.getBankNo()+",余額:" +myAccount.getBalance()); }}12.5.1同步代碼塊

上述代碼在run()方法中,使用“synchronized(this.account){}”對(duì)銀行賬戶的操作代碼進(jìn)行同步,保證某一時(shí)刻只能有一個(gè)線程訪問該賬戶,只有{}里面的代碼執(zhí)行完畢,才釋放對(duì)該賬戶的鎖定。

程序運(yùn)行結(jié)果如下所示:T1操作成功,目前賬戶余額為:2000.0T2操作失敗,余額不足!T3操作成功,目前賬戶余額為:3000.0

賬號(hào):101,余額:3000.012.5.112.5.2同步方法

同步方法是使用synchronized關(guān)鍵字修飾的方法,其聲明的語法格式如下:[訪問修飾符]synchronized返回類型方法名([參數(shù)列表]){ //方法體}

其中:synchronized關(guān)鍵字修飾的實(shí)例方法無須顯式地指定同步監(jiān)視器,同步方法的同步監(jiān)視器是this,即該方法所屬的對(duì)象。一旦一個(gè)線程進(jìn)入一個(gè)實(shí)例的任何同步方法,其他線程將不能進(jìn)入該實(shí)例的所有同步方法,但該實(shí)例的非同步方法仍然能夠被調(diào)用。

使用同步方法可以非常方便地實(shí)現(xiàn)線程安全,一個(gè)具有同步方法的類被稱為“線程安全的類”,該類的對(duì)象可以被多個(gè)線程安全地訪問,且每個(gè)線程調(diào)用該對(duì)象的方法后都將得到正確的結(jié)果。下述案例示例了同步方法的聲明和使用,代碼如下所示。同步方法同步方法【代碼12.11】SynMethodExample.javapackagecom;//增加有同步方法的銀行帳戶類classSynMethod{ //銀行賬號(hào) privateStringbankNo; //銀行余額 privatedoublebalance; //構(gòu)造方法 publicSynMethod(StringbankNo,doublebalance){ this.bankNo=bankNo; this.balance=balance; } //同步方法,存取錢操作 publicsynchronizedvoidaccess(doublemoney){ //如果操作的金額money<0,則代表取錢操作,//同時(shí)判斷賬戶金額是否低于取錢金額 if(money<0&&balance<-money){ System.out.println(Thread.currentThread().getName()+"操作失敗,余額不足!"); return;//返回 }else{12.5.2同步方法 //對(duì)賬戶金額進(jìn)行操作 balance+=money; System.out.println(Thread.currentThread().getName() +"操作成功,目前賬戶余額為:"+balance); try{ //休眠1毫秒 Thread.sleep(1); }catch(InterruptedExceptione){ e.printStackTrace(); } } } publicStringgetBankNo(){ returnbankNo; } publicdoublegetBalance(){ returnbalance; }}12.5.2同步方法publicclassSynMethodExampleextendsThread{ //銀行賬戶 privateSynMethodaccount; //操作金額,正數(shù)為存錢,負(fù)數(shù)為取錢 privatedoublemoney; publicSynMethodExample(Stringname,SynMethodaccount,doublemoney){ super(name); this.account=account; this.money=money; } //線程任務(wù) publicvoidrun(){ //調(diào)用account對(duì)象的同步方法 this.account.access(money); } publicstaticvoidmain(String[]args){ //創(chuàng)建一個(gè)銀行賬戶實(shí)例 SynMethodmyAccount=newSynMethod("1001",5000); //創(chuàng)建多個(gè)線程,對(duì)賬戶進(jìn)行存取錢操作 SynMethodExamplet1=newSynMethodExample("T1",myAccount,-3000); 12.5.2同步方法 SynMethodExamplet2=newSynMethodExample("T2",myAccount,-3000); SynMethodExamplet3=newSynMethodExample("T3",myAccount,1000); //啟動(dòng)線程 t1.start(); t2.start(); t3.start();

//等待所有子線程完成 try{ t1.join(); t2.join(); t3.join(); }catch(InterruptedExceptione){ e.printStackTrace(); } //輸出賬戶信息 System.out.println("賬號(hào):"+myAccount.getBankNo()+",余額:" +myAccount.getBalance()); }}12.5.2同步方法

程序運(yùn)行結(jié)果如下:T1操作成功,目前賬戶余額為:2000.0T2操作失敗,余額不足!T3操作成功,目前賬戶余額為:3000.0

賬號(hào):1001,余額:3000.0

注意:synchronized鎖定的是對(duì)象,而不是方法或代碼塊;synchronized也可以修飾類,當(dāng)用synchronized修飾類時(shí),表示這個(gè)類的所有方法都是synchronized的。12.5.212.5.3同步鎖

同步鎖Lock是一種更強(qiáng)大的線程同步機(jī)制,通過顯式定義同步鎖對(duì)象來實(shí)現(xiàn)線程同步。同步鎖提供了比同步代碼塊、同步方法更廣泛的鎖定操作,實(shí)現(xiàn)更靈活。Lock是控制多個(gè)線程對(duì)共享資源進(jìn)行訪問的工具,能夠?qū)蚕碣Y源進(jìn)行獨(dú)占訪問。每次只能有一個(gè)線程對(duì)Lock對(duì)象加鎖,線程訪問共享資源之前需要先獲得Lock對(duì)象。某些鎖可能允許對(duì)共享資源并發(fā)訪問,如ReadWriteLock(讀寫鎖)。Lock和ReadWriteLock是Java5提供的關(guān)于鎖的兩個(gè)根接口,并為Lock提供了ReentrantLock(可重入鎖)實(shí)現(xiàn)類,為ReadWriteLock提供了ReentrantReadWriteLock實(shí)現(xiàn)類。從Java8開始,又新增了StampedeLock類,可以替代傳統(tǒng)的ReentrantReadWriteLock類。同步鎖同步鎖ReentrantLock類是常用的可重入同步鎖,該類對(duì)象可以顯式地加鎖、釋放鎖。使用ReentrantLock類的步驟如下:

(1)定義一個(gè)ReentrantLock鎖對(duì)象,該對(duì)象是final常量;privatefinalReentrantLocklock=newReentrantLock();

(2)在需要保證線程安全的代碼之前增加“加鎖”操作;lock.lock();

(3)在執(zhí)行完線程安全的代碼后“釋放鎖”。lock.unlock();12.5.3同步鎖下述代碼示例了使用ReentrantLock鎖的基本步驟://1.定義鎖對(duì)象privatefinalReentrantLocklock=newReentrantLock();...//定義需要保證線程安全的方法publicvoidmyMethod(){//2.加鎖lock.lock();try{//需要保證線程安全的代碼...}finally{//3.釋放鎖lock.unlock();}}

其中:加鎖和釋放鎖都需要放在線程安全的方法中;lock.unlock()放在finally語句中,不管發(fā)生異常與否,都需要釋放鎖。12.5.3同步鎖下述案例示例了ReentrantLock同步鎖的使用,代碼如下所示。【代碼12.12】SynLockExample.javapackagecom;importjava.util.concurrent.locks.ReentrantLock;classSynLock{ privateStringbankNo; //銀行賬號(hào) privatedoublebalance;//銀行余額 //定義鎖對(duì)象 privatefinalReentrantLocklock=newReentrantLock(); //構(gòu)造方法 publicSynLock(StringbankNo,doublebalance){ this.bankNo=bankNo; this.balance=balance; } //存取錢操作 publicvoidaccess(doublemoney){ //加鎖 lock.lock(); try{ //如果操作的金額money<0,則代表取錢操作,12.5.3同步鎖 //同時(shí)判斷賬戶金額是否低于取錢金額 if(money<0&&balance<-money){ System.out.println(Thread.currentThread().getName() +"操作失敗,余額不足!"); //返回 return; }else{ //對(duì)賬戶金額進(jìn)行操作 balance+=money; System.out.println(Thread.currentThread().getName() +"操作成功,目前賬戶余額為:"+balance); try{ //休眠1毫秒 Thread.sleep(1); }catch(InterruptedExceptione){ e.printStackTrace(); } } }finally{ 12.5.3同步鎖 //釋放鎖 lock.unlock(); } } publicStringgetBankNo(){ returnbankNo; } publicdoublegetBalance(){ returnbalance; }}12.5.3同步鎖//使用同步鎖的類publicclassSynLockExampleextendsThread{ //銀行賬戶 privateSynLockaccount; //操作金額,正數(shù)為存錢,負(fù)數(shù)為取錢 privatedoublemoney; publicSynLockExample(Stringname,SynLockaccount,doublemoney){ super(name); this.account=account; this.money=money; } //線程任務(wù) publicvoidrun(){ //調(diào)用account對(duì)象的access()方法 this.account.access(money); } publicstaticvoidmain(String[]args){ //創(chuàng)建一個(gè)銀行賬戶實(shí)例 SynLockmyAccount=newSynLock("1001",5000); //創(chuàng)建多個(gè)線程,對(duì)賬戶進(jìn)行存取錢操作12.5.3同步鎖 SynLockExamplet1=newSynLockExample("T1",myAccount,-3000); SynLockExamplet2=newSynLockExample("T2",myAccount,-3000); SynLockExamplet3=newSynLockExample("T3",myAccount,1000); //啟動(dòng)線程 t1.start(); t2.start(); t3.start(); //等待所有子線程完成 try{ t1.join(); t2.join(); t3.join(); }catch(InterruptedExceptione){ e.printStackTrace(); } //輸出賬戶信息 System.out.println("賬號(hào):"+myAccount.getBankNo()+",余額:" +myAccount.getBalance()); }}12.5.3同步鎖程序運(yùn)行結(jié)果如下:T1操作成功,目前賬戶余額為:2000.0T2操作失

溫馨提示

  • 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)論