版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
8.1線程初步
8.2創(chuàng)建線程
8.3線程操作
8.4線程同步
8.5小結(jié)
8.6習(xí)題
8.1.1認(rèn)識(shí)線程
1.解析一個(gè)運(yùn)行結(jié)果
例如,執(zhí)行一個(gè)程序,這個(gè)程序可以一邊顯示cat、dog和hare,一邊計(jì)算并顯示1~5的階乘。
如果在控制臺(tái)完成這樣的功能,按照要求,運(yùn)行結(jié)果如圖8-1所示。8.1線程初步圖8-1兩個(gè)線程同時(shí)工作如何在一個(gè)程序中定義并實(shí)現(xiàn)兩個(gè)功能的代碼段呢?也許這個(gè)問(wèn)題不難解決。可是如何讓它們并發(fā)執(zhí)行,這就是問(wèn)題的關(guān)鍵所在了。完成這個(gè)任務(wù)的主要程序如下:
publicstaticvoidmain(String[]args){
RecognizeThreadth1=newRecognizeThread(); //創(chuàng)建線程1
RecognizeThreadth2=newRecognizeThread(); //創(chuàng)建線程2
th1.start(); //啟動(dòng)線程1
th2.start(); //啟動(dòng)線程2
}publicvoidrun(){//執(zhí)行線程
…
while(true){
if(th1運(yùn)行){
//顯示動(dòng)物名稱
}
else{//(th2運(yùn)行)
//計(jì)算并顯示階乘
}
}
}由于要把程序分成兩個(gè)彼此分離、能獨(dú)立運(yùn)行的子任務(wù),就要考慮線程的創(chuàng)建。Main()方法中創(chuàng)建了兩個(gè)線程th1和th2;RecognizeThread是一個(gè)自定義類,它繼承了Thread類。CPU通過(guò)某些底層機(jī)制進(jìn)行時(shí)間分配,將時(shí)間片段分配給每個(gè)線程。因此就要事先啟動(dòng)線程,等待CPU進(jìn)行時(shí)間分配,這就是start()方法的任務(wù)。當(dāng)某個(gè)線程得到CPU分配的時(shí)間片時(shí),開始執(zhí)行自己的代碼序列,完成相應(yīng)的功能。這些功能代碼被放置在run()方法中,根據(jù)當(dāng)前線程的有效性判斷,然后開始執(zhí)行相關(guān)代碼。
2.線程的基本操作
每個(gè)Java程序至少有一個(gè)線程,被稱為主線程,可以通過(guò)Thread類的currentThread方法看到它。Thread類(線程類)是java.lang包中的一個(gè)專門用來(lái)創(chuàng)建線程和對(duì)線程進(jìn)行操作的類。下面的幾個(gè)例子將運(yùn)用Thread類提供的方法,對(duì)線程進(jìn)行一些基本操作,從而對(duì)線程有一個(gè)更加具體的認(rèn)識(shí)?!纠?-1】得到主線程。
程序清單如下:
publicclassMainThread{
publicstaticvoidmain(String[]args){
//創(chuàng)建一個(gè)線程對(duì)象,并使它得到當(dāng)前正在執(zhí)行的線程對(duì)象的引用
Threadthread=Thread.currentThread();
System.out.println();
//getName():Thread類的方法,得到這個(gè)線程的名字
System.out.println(“mainthreadisnamed:”+thread.getName());
}
}
程序運(yùn)行結(jié)果如圖8-2所示。圖8-2MainThread.java的運(yùn)行結(jié)果可見(jiàn),在Java程序啟動(dòng)時(shí),它有一個(gè)主線程,名字被默認(rèn)為main。如果需要再創(chuàng)建另外的線程,詳細(xì)內(nèi)容將在8.2節(jié)中講解。
【例8-2】命名線程。
程序清單如下:
publicclassSetName{
publicstaticvoidmain(String[]args){
//得到當(dāng)前線程
Threadthread=Thread.currentThread();
System.out.println();System.out.println("mainthreadisnamed:"+thread.getName());
//將這個(gè)線程的名字改為參數(shù)的名字
thread.setName("Java");
System.out.println();
System.out.println("mainthreadisnownamed:"+thread.getName());
}
}
程序運(yùn)行結(jié)果如圖8-3所示。圖8-3SetName.java的運(yùn)行結(jié)果命名線程是區(qū)分線程的方法之一。
有時(shí)會(huì)希望暫停某個(gè)線程的執(zhí)行,這時(shí)可以用sleep方法來(lái)實(shí)現(xiàn):將指定時(shí)間量傳遞給sleep方法,線程將暫停,直至此時(shí)間結(jié)束再繼續(xù)執(zhí)行。sleep方法的使用有以下兩種形式:
staticvoidsleep(longn):暫停n毫秒。
staticvoidsleep(longm,intn):暫停1000000×m+n納秒。
【例8-3】暫停線程。
publicclassSleepThread{
publicstaticvoidmain(String[]args){
try{
System.out.println("Let\'s");//主線程暫停1秒鐘,再繼續(xù)往下執(zhí)行
Thread.sleep(1000);
System.out.println("study");
Thread.sleep(1000);//同上
System.out.println("conception");
Thread.sleep(1000);//同上
System.out.println("of");
Thread.sleep(1000);//同上
System.out.println("thread");
}
catch(InterruptedExceptione){}
}
}
程序運(yùn)行結(jié)果如圖8-4所示。圖8-4SleepThread.java的運(yùn)行結(jié)果關(guān)于這個(gè)程序的說(shuō)明:
(1)由于sleep方法是靜態(tài)方法,因此在程序中可以采用“Thread.sleep(1000)”的形式,表明將主線程暫停1000毫秒。
(2)程序在執(zhí)行時(shí),結(jié)果的呈現(xiàn)是有暫停的。
(3)在調(diào)用sleep方法時(shí),必須要捕獲異常。8.1.2線程的生命周期
Java語(yǔ)言使用Thread類及其子類的對(duì)象來(lái)表示線程。新建的線程在它的一個(gè)完整的生命周期中通常要經(jīng)歷新生、就緒、運(yùn)行、阻塞和死亡等五種狀態(tài)。
1)新生狀態(tài)
當(dāng)用new關(guān)鍵字和某線程類的構(gòu)造方法創(chuàng)建一個(gè)線程對(duì)象后,這個(gè)線程對(duì)象就處于新生狀態(tài),此時(shí)它已經(jīng)有了相應(yīng)的內(nèi)存空間,并被初始化。處于該狀態(tài)的線程可通過(guò)調(diào)用start()方法進(jìn)入就緒狀態(tài)。
2)就緒狀態(tài)
處于就緒狀態(tài)的線程已經(jīng)具備了運(yùn)行的條件,但尚未分配到CPU資源,因而它將進(jìn)入線程隊(duì)列排隊(duì),等待系統(tǒng)為它分配CPU。一旦獲得CPU資源,則該線程進(jìn)入運(yùn)行狀態(tài),并自動(dòng)調(diào)用自己的run()方法。此時(shí),它脫離創(chuàng)建它的主線程,獨(dú)立開始了自己的生命周期。
3)運(yùn)行狀態(tài)
進(jìn)入運(yùn)行狀態(tài)的線程即可執(zhí)行自己的run()方法中的代碼。若遇到下列情況之一,則將終止run()方法的執(zhí)行:
(1)終止操作。調(diào)用當(dāng)前的stop()方法或destroy()方法進(jìn)入死亡狀態(tài)。
(2)等待操作。調(diào)用當(dāng)前線程的join(millis)方法或wait(millis)方法進(jìn)入阻塞狀態(tài)。當(dāng)線程進(jìn)入阻塞狀態(tài)時(shí),在millis毫秒內(nèi)可由其他線程調(diào)用notify()或notifyAll()方法將其喚醒,進(jìn)入就緒狀態(tài);在millis毫秒內(nèi)若不喚醒,則須等待到當(dāng)前線程結(jié)束。
(3)睡眠操作。調(diào)用sleep(millis)方法來(lái)實(shí)現(xiàn)。當(dāng)前線程停止執(zhí)行后即處于阻塞狀態(tài),待睡眠millis毫秒之后重新進(jìn)入就緒狀態(tài)。
(4)掛起操作。通過(guò)調(diào)用suspend()方法來(lái)實(shí)現(xiàn)。當(dāng)前線程一旦掛起,即進(jìn)入阻塞狀態(tài),只有當(dāng)其他線程調(diào)用當(dāng)前線程的resume()方法后,才能使其進(jìn)入就緒狀態(tài)。
(5)退讓操作。通過(guò)調(diào)用yield()方法,“暗示”線程調(diào)度機(jī):當(dāng)前線程的工作已經(jīng)快完成,可以讓別的線程使用CPU了;而當(dāng)前線程則停止執(zhí)行,進(jìn)入就緒狀態(tài)。
(6)當(dāng)前線程要求輸入/輸出時(shí),進(jìn)入阻塞狀態(tài)。
(7)若分配給當(dāng)前線程的時(shí)間片已用完,則當(dāng)前線程進(jìn)入就緒狀態(tài)。若當(dāng)前線程的run()方法執(zhí)行完畢,則線程進(jìn)入死亡狀態(tài)。
4)阻塞狀態(tài)
一個(gè)正在執(zhí)行的線程在某些特殊情況下,如執(zhí)行了join()或sleep()方法,或等待I/O設(shè)備的使用權(quán),那么它將讓出CPU并暫時(shí)終止自己的執(zhí)行,進(jìn)入阻塞狀態(tài)。阻塞時(shí)該線程不能進(jìn)入就緒隊(duì)列,只有當(dāng)引起阻塞的原因被消除時(shí),才能轉(zhuǎn)入就緒狀態(tài),重新進(jìn)入線程隊(duì)列中排隊(duì)等待CPU資源,以便從原來(lái)終止處繼續(xù)運(yùn)行。
5)死亡狀態(tài)
這是線程最后的狀態(tài),處于死亡狀態(tài)的線程將永遠(yuǎn)不再執(zhí)行。線程死亡有兩個(gè)原因:一是正常運(yùn)行的線程完成了它的全部工作;二是線程被提前強(qiáng)制性地終止,例如,通過(guò)執(zhí)行stop()或destroy()方法來(lái)終止線程。不過(guò)Thread類的stop()方法已經(jīng)被廢棄,因?yàn)樗话踩共僮飨到y(tǒng)訪問(wèn)監(jiān)控器處于不穩(wěn)定的狀態(tài)。每個(gè)Java程序都有一個(gè)默認(rèn)的主線程。對(duì)于JavaApplication程序,在執(zhí)行main()方法時(shí)會(huì)啟動(dòng)一個(gè)線程,這就是“主線程”,它負(fù)責(zé)執(zhí)行main()方法;對(duì)于JavaApplet程序,當(dāng)瀏覽器內(nèi)置的JVM創(chuàng)建其主類的一個(gè)對(duì)象后,會(huì)啟動(dòng)一個(gè)專門的線程——稱為JavaApplet的主線程,它讓主類的對(duì)象調(diào)用start()方法,開始執(zhí)行JavaApplet。要想實(shí)現(xiàn)多線程,必須在主線程中創(chuàng)建其他新的線程對(duì)象。8.2創(chuàng)建線程8.2.1繼承Thread類來(lái)創(chuàng)建線程
Thread類(線程類)是java.lang包中的一個(gè)專門用來(lái)創(chuàng)建線程并對(duì)線程進(jìn)行操作的類。這個(gè)類已經(jīng)具有創(chuàng)建和運(yùn)行線程所必要的架構(gòu)。Java在Thread類中定義了許多方法,有助于更好地運(yùn)用和處理線程。這些方法可分為以下四組:
(1)構(gòu)造方法:用于創(chuàng)建用戶的線程對(duì)象。
(2)?run()方法:用于定義用戶線程所要執(zhí)行的操作。
(3)改變線程狀態(tài)的方法:如start()、sleep()、stop()、suspend()、resume()、yield()和wait()等方法,是最常用的一組方法。
(4)其他方法:如setPriority()、setName()等。
【例8-4】有一個(gè)數(shù)從0開始不斷遞增,顯示遞增過(guò)程。
將這個(gè)過(guò)程設(shè)計(jì)成一個(gè)線程,并在控制臺(tái)運(yùn)行。實(shí)現(xiàn)這個(gè)設(shè)計(jì)過(guò)程的步驟如下:
(1)繼承Thread類,通過(guò)其構(gòu)造線程對(duì)象,這時(shí)線程處于新生狀態(tài)。
(2)利用start()方法啟動(dòng)該線程,使其進(jìn)入線程隊(duì)列排隊(duì),等待系統(tǒng)為它分配CPU,即處于就緒狀態(tài)。
(3)覆蓋Thread類的run()方法,從而完成數(shù)據(jù)的遞增和顯示。當(dāng)線程得到CPU資源,處于運(yùn)行狀態(tài)時(shí),會(huì)自動(dòng)調(diào)用這個(gè)方法來(lái)執(zhí)行。程序清單如下:
publicclassSimpleThreadextendsThread{//繼承Thread類
intcount=0;
publicstaticvoidmain(String[]args){
SimpleThreadaddThread=newSimpleThread(); //創(chuàng)建一個(gè)線程
addThread.start(); //啟動(dòng)線程
}publicvoidrun(){
//覆蓋run()方法:遞增并顯示
while(true){
try{
sleep(500);
//主線程休眠500毫秒
}
catch(InterruptedExceptione){}
System.out.println(count++);
}
}
}
程序運(yùn)行結(jié)果如圖8-5所示。圖8-5SimpleThread.java的運(yùn)行結(jié)果這個(gè)結(jié)果將不停地顯示下去。事實(shí)上run()方法總會(huì)有某種形式的循環(huán),使得線程一直運(yùn)行下去直到不再需要,所以要設(shè)定跳出循環(huán)的條件(在后面的例子中會(huì)看到)。
【例8-5】重新看本章的第一個(gè)程序。這個(gè)程序可以一邊顯示cat、dog和hare,一邊計(jì)算并顯示1~5的階乘。程序清單如下:
publicclassRecognizeThreadextendsThread{
intcount=0,num=4,i=1,temp=1;
String[]array={“cat”,“dog”,“hare”};
publicstaticvoidmain(String[]args){
RecognizeThreadth1=newRecognizeThread();
//創(chuàng)建線程th1
RecognizeThreadth2=newRecognizeThread();
//創(chuàng)建線程th2//給線程起名——在執(zhí)行run()方法時(shí),可根據(jù)當(dāng)前執(zhí)行線程的名稱來(lái)判斷執(zhí)行
//哪個(gè)代碼段
th1.setName("thread1");
th2.setName("thread2");
th1.start();//啟動(dòng)線程th1
th2.start();//啟動(dòng)線程th2
}
publicvoidrun(){//執(zhí)行線程
while(true){//線程休眠500毫秒,控制顯示速度
try{
sleep(500);
}
catch(InterruptedExceptione){}
//如果當(dāng)前執(zhí)行的是線程th1,則顯示動(dòng)物名稱,否則計(jì)算并顯示階乘
if((Thread.currentThread().getName()).equals("thread1")){
System.out.println(array[count++]);
if(count>2)count=0;
}else{
temp=temp*i;
System.out.println(i+"!="+temp);
i++;
if(i>4){
i=1;
temp=1;
}
}
}
}
}例8-5使用了繼承Thread類的方法。當(dāng)一個(gè)類必須繼承另一個(gè)類,而且又要完成線程的控制時(shí),就不能繼承Thread類,因?yàn)镴ava不支持多重繼承。這時(shí)可以這樣考慮:定義一個(gè)A類,使得A繼承Thread類;在主類中完成線程的創(chuàng)建和啟動(dòng);在A中完成run()方法的覆蓋。例8-6就是按照這個(gè)思路設(shè)計(jì)的。
【例8-6】設(shè)計(jì)一個(gè)圖形界面,用一個(gè)按鈕啟動(dòng)計(jì)數(shù)器做遞增運(yùn)算,并在文本框里顯示數(shù)據(jù)(從0開始計(jì)數(shù))。importjava.awt.*;
importjavax.swing.*;
importjava.awt.event.*;
publicclassComplicateThreadextendsJAppletimplementsActionListener{
ThreadClassaddThread; //聲明線程對(duì)象
JTextFieldtf=newJTextField(10); //設(shè)計(jì)顯示文本框
JButtonbt=newJButton("Increase"); //設(shè)計(jì)按鈕publicvoidinit(){
Containercp=getContentPane();
cp.setLayout(newFlowLayout());//完成容器布局的設(shè)計(jì)
cp.add(tf);
//加載組件
cp.add(bt);
bt.addActionListener(this);
//完成事件監(jiān)聽
}publicvoidactionPerformed(ActionEvente){
if(addThread==null){
//判斷是否第一次操作按鈕
addThread=newThreadClass(); //創(chuàng)建線程
addThread.start(); //啟動(dòng)線程
}
}
//定義內(nèi)部類ThreadClass,使其成為線程類;內(nèi)部類使tf可用classThreadClassextendsThread{
intcount=0; //計(jì)數(shù)器初始化
publicvoidrun(){
while(true){
try{
sleep(500);
}
catch(InterruptedExceptione){}
tf.setText(Integer.toString(count++));
//計(jì)數(shù)并顯示
}
}
}
}
程序運(yùn)行結(jié)果如圖8-6所示。圖8-6ComplicateThread.java的運(yùn)行結(jié)果在這個(gè)例題中,ComplicateThread類必須繼承JApplet類才能設(shè)計(jì)成圖形界面,把線程類的定義交給了其他類去完成。如果ComplicateThread類本身又想成為一個(gè)線程類,完成線程的整個(gè)控制時(shí),如何設(shè)計(jì)呢?這就需要用到創(chuàng)建線程的另外一種方法——實(shí)現(xiàn)Runnable接口。8.2.2實(shí)現(xiàn)Runnable接口來(lái)創(chuàng)建線程
創(chuàng)建線程的另一個(gè)途徑是實(shí)現(xiàn)Runnable接口。Runnable接口提供了一個(gè)名為run()的方法,用戶新建線程的操作代碼應(yīng)該放在這個(gè)方法中去定義。當(dāng)用戶程序需要建立新線程時(shí),只需將實(shí)現(xiàn)了Runnable接口類的對(duì)象作為參數(shù)傳遞給Thread類的構(gòu)造方法即可。與Runnable相關(guān)的Thread類的構(gòu)造方法如下:
Thread(Runnabletarget):使用Runnable對(duì)象構(gòu)造一個(gè)新的Thread對(duì)象。
Thread(Runnabletarget,Stringname):使用Runnable對(duì)象和一個(gè)名字構(gòu)造一個(gè)新的Thread對(duì)象?!纠?-7】顯示當(dāng)前時(shí)間,實(shí)現(xiàn)Runnable接口。
importjavax.swing.*;
importjava.awt.*;
importjava.util.*;//定義了date類
publicclassCreatRunnableextendsJAppletimplementsRunnable{
Threadth;
Stringstr="";//覆蓋Runnable接口中的run()方法
publicvoidrun(){
while(true){
try{
th.sleep(1000);
}
catch(InterruptedExceptione){}
repaint();
}
}//初始化方法,完成線程的創(chuàng)建
publicvoidinit(){
if(th==null){
th=newThread(this,"clock");
th.start();
}
}
//繪制日期
publicvoidpaint(Graphicsg){g.setFont(newFont(“TimesRoman”,Font.PLAIN,18));
g.setColor(Color.WHITE);
//用白色把上一次顯示的字符串重寫一遍,達(dá)到清理痕跡的效果
g.drawString(str,10,10);
//str存放的是剛剛顯示過(guò)的字符串
g.setColor(Color.BLUE);//用藍(lán)色顯示新字符串的值
str=(newDate()).toString();
//得到當(dāng)前時(shí)間,并轉(zhuǎn)化成日期類的文字
g.drawString(str,10,10);//顯示當(dāng)前時(shí)間
}
}
程序運(yùn)行結(jié)果如圖8-7所示。圖8-7CreatRunnable.java的運(yùn)行結(jié)果8.3線程操作8.3.1線程等待
有兩個(gè)線程A和B,如果線程A對(duì)線程B執(zhí)行了B.join(),則線程A將被掛起,直到目標(biāo)線程B結(jié)束后才繼續(xù)執(zhí)行。這是線程間的等待關(guān)系,也算是一種協(xié)作。在這個(gè)過(guò)程中,可以利用線程的isAlive()方法檢查線程是否是活躍的。如果線程正在執(zhí)行,則該方法返回false,否則返回true?!纠?-8】線程等待示例。
classCustomThreadextendsThread{//定義了一個(gè)線程類
publicCustomThread(Stringname){
super(name);
start();
}
publicvoidrun(){//完成線程在運(yùn)行時(shí)的信息顯示
try{
for(inti=1;i<3;i++){System.out.println((Thread.currentThread()).getName()+"thread:"+i+"次.");
sleep(500);
}
}
catch(InterruptedExceptione){}
System.out.println((Thread.currentThread()).getName()+"threadending!");
}
}publicclassMethod_Join{//公共類的定義
publicstaticvoidmain(String[]args){
CustomThreadth1=newCustomThread("animal");
CustomThreadth2=newCustomThread("plant");
System.out.println("th1:"+th1.isAlive());
/*
try{
th1.join();//等待th1完成
th2.join();//等待th2完成
}catch(InterruptedExceptione){}
System.out.println("th1:"+th1.isAlive());
*/
System.out.println("Mainmethodend!All2threadsareended.");
}
}
程序運(yùn)行結(jié)果如圖8-8所示。圖8-8Method_Join.java的運(yùn)行結(jié)果主線程和線程th1、th2在分配CPU的時(shí)間內(nèi)輪流執(zhí)行。如果將程序中的注釋去掉,對(duì)th1和th2分別執(zhí)行join()方法,則主線程會(huì)停止運(yùn)行,直到th1和ht2執(zhí)行完后再繼續(xù)運(yùn)行。運(yùn)行結(jié)果如圖8-9所示。圖8-9線程等待的運(yùn)行結(jié)果8.3.2停止線程
通過(guò)Thread的start()方法啟動(dòng)線程后,將調(diào)用線程類的run()方法運(yùn)行線程,當(dāng)run()方法運(yùn)行結(jié)束時(shí),線程也就結(jié)束了。有時(shí)線程的run()方法會(huì)被寫成無(wú)限循環(huán)的形式,這就意味著,除非有某個(gè)條件使得run()終止,否則它將永遠(yuǎn)運(yùn)行下去。因?yàn)門hread類的stop()方法已經(jīng)被廢棄,不能通過(guò)它來(lái)終止線程,所以要設(shè)定跳出循環(huán)的條件。
例8-9所示程序StopThread.java介紹了停止線程的方法,即停止一個(gè)每隔200毫秒打印一條消息的線程。【例8-9】StopThread.java程序示例。
classCustomThreadextendsThread{//定義了一個(gè)線程類
intcounter=1;
booleanflag=true;//設(shè)置標(biāo)志
publicvoidrun(){
System.out.println("Threadbegin:");
while(flag){
try{System.out.println("Threadrun:"+counter++);
Thread.sleep(200);//每隔200毫秒顯示一次信息
}
catch(InterruptedExceptione){}
}
}
publicvoidstopThread(){//改變標(biāo)志,使線程停止運(yùn)行
flag=false;
System.out.println("Threadisvoer.");
}
}publicclassStopThread{//公共類的定義
publicstaticvoidmain(String[]args){
CustomThreadth=newCustomThread();
th.start();
try{
Thread.sleep(1000);
//主線程暫停1秒鐘,讓線程th運(yùn)行
}
catch(InterruptedExceptione){}
th.stopThread();//終止th的運(yùn)行
}
}
程序運(yùn)行結(jié)果如圖8-10所示。圖8-10StopThread.java的運(yùn)行結(jié)果在這個(gè)程序里,改變循環(huán)條件是通過(guò)一個(gè)自定義方法來(lái)實(shí)現(xiàn)的——stopThread()。主線程創(chuàng)建并啟動(dòng)了線程th,這時(shí)主線程暫停1秒鐘。在這1秒鐘里,線程th每隔200毫秒打印一條信息。1秒鐘過(guò)后,主線程執(zhí)行了th.stopThread();語(yǔ)句,改變了循環(huán)標(biāo)志,使得線程th終止。
在實(shí)際編程中,循環(huán)條件要根據(jù)不同的程序意圖進(jìn)行設(shè)置。設(shè)定循環(huán)標(biāo)志僅僅是其中的一種方法。8.3.3線程調(diào)度
在Java系統(tǒng)中,運(yùn)行的每個(gè)線程都有優(yōu)先級(jí)。線程的優(yōu)先級(jí)能告訴調(diào)度程序該線程的重要性如何。盡管CPU處理現(xiàn)有線程集的順序是不確定的,但是如果有許多線程被阻塞并在等待運(yùn)行,那么調(diào)度程序?qū)A向于讓優(yōu)先級(jí)最高的線程先運(yùn)行。然而,這并不意味著優(yōu)先級(jí)較低的線程將得不到執(zhí)行,它僅僅是執(zhí)行的頻率較低,即優(yōu)先級(jí)不會(huì)導(dǎo)致死鎖。
JDK有10個(gè)優(yōu)先級(jí)別(用1~10表示),數(shù)值越大,優(yōu)先級(jí)越高。Java用標(biāo)識(shí)符常量MIN_PRIORITY表示優(yōu)先級(jí)為1,NORM_PRIORITY表示優(yōu)先級(jí)為5,MAX_PRIORITY表示優(yōu)先級(jí)為10,未設(shè)定優(yōu)先級(jí)的線程其優(yōu)先級(jí)取缺省值5。其他級(jí)別的優(yōu)先級(jí)既可以直接用1~10之間的正整數(shù)來(lái)設(shè)置,也可以在標(biāo)識(shí)符常量的基礎(chǔ)上加一個(gè)常數(shù)。例如,下面的語(yǔ)句將線程優(yōu)先級(jí)設(shè)置為8。
SetPriority(Thread.NORM_PRIORITY+3);
例8-10中,程序Priority.java演示了優(yōu)先級(jí)的設(shè)置。在這個(gè)程序里,有兩個(gè)線程用來(lái)從0開始計(jì)數(shù),經(jīng)過(guò)5秒鐘后,顯示兩個(gè)線程的最終計(jì)數(shù)值。如果把兩個(gè)線程的優(yōu)先級(jí)都定義成NORM_PRIORITY,運(yùn)行結(jié)果會(huì)大體相當(dāng)。
【例8-10】Priority.java程序示例。classCustomThreadimplementsRunnable
{ //定義了一個(gè)線程類
Threadthread;
intcounter=0;
booleanflag=true;
publicCustomThread(intp){
thread=newThread(this); //創(chuàng)建線程
thread.setPriority(p); //設(shè)置線程的優(yōu)先級(jí)
thread.start(); //啟動(dòng)線程
}publicvoidrun(){
//完成線程在運(yùn)行時(shí)的信息顯示
while(flag){
counter++;
}
}
//定義stop()方法:改變標(biāo)志,使線程停止運(yùn)行
publicvoidstop(){
flag=false;
}
}publicclassPriority{//公共類的定義
publicstaticvoidmain(String[]args){
CustomThreadth1=newCustomThread(Thread.NORM_PRIORITY);
CustomThreadth2=newCustomThread(Thread.NORM_PRIORITY);
try{
Thread.sleep(5000);
//主線程暫停5秒鐘,即讓th1和th2計(jì)數(shù)5秒
}
catch(InterruptedExceptione){}
th1.stop();//終止th1的運(yùn)行
th2.stop();//終止th2的運(yùn)行
System.out.println("th1counted::"+th1.counter);
System.out.println("th2counted::"+th2.counter);
}
}
程序運(yùn)行結(jié)果如圖8-11所示。圖8-11Priority.java的運(yùn)行結(jié)果程序中有兩條語(yǔ)句對(duì)線程設(shè)置優(yōu)先級(jí):
CustomThreadth1=newCustomThread(Thread.NORM_PRIORITY);
CustomThreadth2=newCustomThread(Thread.NORM_PRIORITY);
現(xiàn)在把它們改成:
CustomThreadth1=newCustomThread(Thread.NORM_PRIORITY-2);
CustomThreadth2=newCustomThread(Thread.NORM_PRIORITY+2);
再運(yùn)行程序,結(jié)果如圖8-12所示。圖8-12改變了優(yōu)先級(jí)的運(yùn)行結(jié)果8.4.1程序分析
例8-11中,程序SyncThread1.java完成如下功能:一個(gè)線程完成偶數(shù)的計(jì)算和顯示;另一個(gè)線程完成對(duì)所求偶數(shù)的判斷,并顯示判斷結(jié)果。
【例8-11】SyncThread1.java程序示例。
importjava.awt.*;
importjavax.swing.*;
importjava.awt.event.*;
publicclassSyncThread1extendsJAppletimplementsActionListener{8.4線程同步Eventh1;
Watchth2;
JTextFieldt1=newJTextField(10);
JTextFieldt2=newJTextField(15);
JTextFieldt3=newJTextField(15);
JButtonbt=newJButton("開始統(tǒng)計(jì)");
//線程類的定義:run()方法完成求得偶數(shù)并顯示
classEvenextendsThread{
intcount=0,i=0;
booleanflag=true;publicvoidrun(){
while(true){
i++;
Integer.toString(i);
//沒(méi)有實(shí)際的意義,僅僅為了增加效果
i++;
t1.setText(Integer.toString(i));
//將求得的偶數(shù)顯示在文本框里
try{
sleep(1);
}catch(InterruptedExceptione){}
}
}
publicvoidjudgeEven(){//判斷偶數(shù)
if(i%2!=0){
//將非偶數(shù)顯示在文本框里(t2)
t2.setText(Integer.toString(i));
//將統(tǒng)計(jì)的非偶數(shù)的個(gè)數(shù)信息顯示在文本框里(t3)
t3.setText("Noteven"+Integer.toString(++count)+"times");
flag=false;
}
//將偶數(shù)的信息顯示在文本框里(t3)
if(flag==true)t3.setText("Alwayseveneachtimes.");
}
}//線程類的定義:調(diào)用偶數(shù)判斷的方法
classWatchextendsThread{
publicvoidrun(){
while(true){
th1.judgeEven();
//通過(guò)th1對(duì)象,調(diào)用judgeEven()方法
try{
sleep(1);
}catch(InterruptedExceptione){}
}
}
}
publicvoidinit(){
Containercp=getContentPane();
cp.setLayout(newFlowLayout());
cp.add(t1);
cp.add(t2);
cp.add(t3);
cp.add(bt);
bt.addActionListener(this);
}publicvoidactionPerformed(ActionEvente){
if(th1==null){//創(chuàng)建并啟動(dòng)求偶數(shù)線程
th1=newEven();
th1.start();
}
if(th2==null){//創(chuàng)建并啟動(dòng)偶數(shù)判斷線程
th2=newWatch();
th2.start();
}
}
}
程序運(yùn)行結(jié)果如圖8-13所示。圖8-13SyncThread1.java的運(yùn)行結(jié)果求偶數(shù)方法中的關(guān)鍵步驟如下:
publicvoidrun(){
while(true){
i++;
i++;
t1.setText(Integer.toString(i));
}由上述代碼可知,文本框里的結(jié)果毫無(wú)疑問(wèn)是偶數(shù),即每次循環(huán)體結(jié)束時(shí),i的值肯定是偶數(shù)。但是從結(jié)果上看,i不總是偶數(shù),而且隨著程序的繼續(xù)運(yùn)行,非偶數(shù)的個(gè)數(shù)會(huì)越來(lái)越多。這是什么原因呢?問(wèn)題就出在下面兩條語(yǔ)句上:
i++;
i++;在執(zhí)行這兩條語(yǔ)句前或者執(zhí)行完這兩條語(yǔ)句后,i肯定是偶數(shù)??墒?,當(dāng)線程th1執(zhí)行完第一條i++語(yǔ)句后可能暫停,在這一時(shí)刻,i是奇數(shù)。接著th2線程接著執(zhí)行,對(duì)i進(jìn)行偶數(shù)判斷,意外發(fā)生了。仔細(xì)分析,意外的發(fā)生是由于兩個(gè)線程(th1和th2)在需要訪問(wèn)統(tǒng)一資源i時(shí),th1在不該停的地方停了下來(lái),導(dǎo)致th2取得了錯(cuò)誤數(shù)據(jù)。應(yīng)該的執(zhí)行順序是:th1執(zhí)行完了以后,再允許th2執(zhí)行;同樣,th2顯示了關(guān)于i的信息后,再執(zhí)行th1,引起i值的變化。防止其他線程進(jìn)行某種操作,直到當(dāng)前線程完成此操作,這就稱為同步。在同步機(jī)制中,將多個(gè)線程都要進(jìn)行操作的資源稱為臨界資源,將訪問(wèn)臨界資源的程序段稱為臨界區(qū)。換另外一種說(shuō)法,在某一時(shí)刻,只能允許一個(gè)線程訪問(wèn)對(duì)象的臨界區(qū),這叫做線程的互斥。
Java以提供關(guān)鍵字synchronized的形式,為防止資源沖突提供了內(nèi)置支持。通常在代碼前加上這條鎖語(yǔ)句,就保證了在一段時(shí)間內(nèi)只能有一個(gè)線程運(yùn)行這段代碼。因?yàn)殒i語(yǔ)句產(chǎn)生了一種互相排斥的效果,所以這種機(jī)制常常稱為“互斥量”。
Java解決同步問(wèn)題的方法有兩種:同步方法和同步代碼塊。
另外,程序中有下面的語(yǔ)句:
i++;
Integer.toString(i);
i++;
語(yǔ)句Integer.toString(i);在這里僅僅是為了讓CPU多執(zhí)行一條語(yǔ)句,加大兩條i++語(yǔ)句在時(shí)間上的執(zhí)行空間,使得線程停在它們中間的頻率加大,增強(qiáng)程序的運(yùn)行效果。8.4.2同步方法
同步方法是在可能會(huì)發(fā)生同步問(wèn)題的run()和judgeEven()方法之前加上synchronized關(guān)鍵字,即
publicsynchronizedvoidrun(){
…
}
publicsynchronizedvoidjudgeEven(){
…
}這兩個(gè)方法都含在Even類中,程序中的線程在訪問(wèn)它們時(shí)使用同一個(gè)對(duì)象th1。當(dāng)線程訪問(wèn)其中的一個(gè)方法時(shí),就會(huì)給所有的方法加鎖,在加鎖的線程執(zhí)行完畢后,被鎖住的所有方法將自動(dòng)開鎖,這時(shí)才允許別的線程訪問(wèn)同步方法。
按照這種解決方式,程序運(yùn)行結(jié)果如圖8-14所示。圖8-14用同步方法解決的運(yùn)行結(jié)果圖8-14中的兩個(gè)文本框里沒(méi)有顯示任何信息。由于兩個(gè)方法同時(shí)被鎖住,且求偶數(shù)的方法是死循環(huán)(while(true){…})
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024年物業(yè)管理服務(wù)合同:高檔住宅區(qū)定制版
- 幾百幾十?dāng)?shù)乘以一位數(shù)過(guò)關(guān)考核例題大全附答案
- 2024年版房地產(chǎn)項(xiàng)目規(guī)劃設(shè)計(jì)合同
- 2025年度智能安防平臺(tái)施工工程協(xié)議2篇
- 2025版新能源分公司合作成立與市場(chǎng)開發(fā)協(xié)議3篇
- 2024年版物業(yè)服務(wù)合同:物業(yè)公司與居民之間的權(quán)利義務(wù)約定
- 2024年版項(xiàng)目總經(jīng)理職務(wù)聘請(qǐng)協(xié)議版B版
- 2024年甲乙雙方股權(quán)轉(zhuǎn)讓合同
- 美容院固化地坪施工方案
- 2024年度規(guī)范型回遷房買賣合同協(xié)議書(含租賃權(quán))3篇
- 企業(yè)員工上下班交通安全培訓(xùn)(簡(jiǎn)詳共2份)
- 城市高密度建成區(qū)合流制溢流污染系統(tǒng)研究-黃孝河機(jī)場(chǎng)河水環(huán)境綜合治理項(xiàng)目實(shí)踐
- T∕ZSQX 008-2020 建設(shè)工程全過(guò)程質(zhì)量行為導(dǎo)則
- ISO-IEC17025-2017實(shí)驗(yàn)室管理體系全套程序文件
- 業(yè)務(wù)員手冊(cè)內(nèi)容
- pH值的測(cè)定方法
- 輸出軸的機(jī)械加工工藝規(guī)程及夾具設(shè)計(jì)
- 元旦文藝匯演校長(zhǎng)致辭
- 國(guó)家開放大學(xué)電大本科《管理案例分析》2023-2024期末試題及答案試卷編號(hào):1304
- 離合器接合叉機(jī)械工藝說(shuō)明書
- PWM脈寬直流調(diào)速系統(tǒng)設(shè)計(jì)及 matlab仿真驗(yàn)證
評(píng)論
0/150
提交評(píng)論