多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > php教程 > 牛刀小試 - 詳解Java多線程

牛刀小試 - 詳解Java多線程

來源:程序員人生   發布時間:2015-02-10 08:19:44 閱讀次數:2487次

線程與多線程的概念

關于線程與多線程的較詳細的理解可以參考:線程的解釋 和多線程的解釋。

而我們要做的是,對其進行“精煉"。我們每天都在和電腦、手機打交道,每天都在使用各種各樣的利用軟件。

打開上電腦的任務管理器,就能夠看到有1項名為"進程"的欄目,點擊到里面可能就會發現1系列熟習的名稱:QQ,360等等。

所以首先知道了,QQ、360之類的利用軟件在計算機上被稱為1個進程。


而1個利用程序都會有自己的功能,用以履行這些進程當中的個別功能的程序履行流就是所謂的線程。

所以,線程有時候也被稱為輕量級進程,是程序履行流當中的最小單元。

線程的劃分尺度小于進程,其不能夠獨立履行,必須依存在利用程序中,由利用程序提供多個線程履行控制。

進程在履行進程中具有獨立的內存單元,而多個線程同享內存,所以能極大地提高了程序的運行效力

所以簡而言之的概括的話,就是:1個程序最少有1個進程,1個進程最少有1個線程。


以360殺毒來講,里面的1項功能任務“電腦體檢”就是該利用程序進程中的1個線程任務。

而除此任務以外,我們還可以同時進行多項操作。例如:“木馬查殺”、“電腦清算”等。

那末,以上同時進行的多項任務就是所謂的存活在360利用程序進程中的多線程并發。


多線程的利與弊

多線程的有益的地方,不言而喻。在傳統的程序設計語言中,同1時刻只能履行單任務操作,效力非常低。

假定在某個任務履行的進程中產生梗塞,那末下1個任務就只能1直等待,直至該任務履行完成后,才能接著履行。

而得益于多線程能夠實現的并發操作,即便履行進程中某個線程因某種緣由產生阻塞,也不會影響到其它線程的履行。

也就是說,多線程并發技術帶來的最大好處就是:很大程度上提高了程序的運行效力。


似乎百里而無1害的多線程并發技術,還有弊端嗎?從某種程度上來講,也是存在的:會致使任務履行效力的下降。

之所以這樣講,是由于所謂的“并發”其實不是真正意義上的并發,而是CPU在多個線程之間做著快速切換的操作。

但CPU的運算速度肯定是遠遠高于人類的思惟速度,所以就帶來了1種“并發”的錯覺。

那就不難想象了:假定某1進程中,線程A與線程B并發履行,CPU要做的工作就是:

不斷快速且隨機的在兩個線程之間做著切換,分別處理對應線程上的線程任務,直到兩個線程上的任務都被處理完成。


那末,也就能夠斟酌這樣的情況:CPU履行完本來線程A的線程任務只需要5秒;但如今由于另外一個線程B的并發加入。

CPU則不能不分出1部份時間切換到線程B上進行運算處理。因而可能CPU完成該線程任務A的時間反而延長到了7秒。

所以所謂的效力下降,就是指針對某單個任務的履行效力而言的。

也就是說,如果在多線程并發操作時,如果有某個線程的任務你認為優先級很高。那末則可以:

通過設置線程優先級或通過代碼控制等手段,來保證該線程享有足夠的“特權”。

注:Java中設置線程優先級,實際上也只是設置的優先級越大,該線程被CPU隨機訪問到的幾率會相對高1些。


這個進程可以替換成1些實際生活中的情形來進行思考。快過年了,以家庭團圓為例。

假定你除準備炒1桌子美味的菜肴以外,過年自然還要有1頓熱騰騰的餃子。那末:

傳統單任務的操作進程可以被理解為:先把準備的菜肴都做好;菜都端上桌后便開始煮餃子。

這樣做的壞處就是:如果在炒菜的中途產生1些意外情況,那末隨著炒菜動作的暫停。煮餃子的動作也將被無窮期延后。

而對應于多線程并發的操作就是:1邊炒菜,1邊煮餃子。這時候你就是CPU,你要做的動作多是這樣的:

炒菜的中途你能會抽空去看看鍋里的餃子煮好沒有;發現沒有煮好,又回來繼續炒菜。炒好1道菜后,再去看看餃子能出鍋了沒。

由此你發現,你做的工作與CPU處理多線程并發的工作是1樣的:不斷的在“煮餃子”與“炒菜”兩個任務之間做著切換。


線程的周期及狀態

Java中線程的全部生命周期基本可以劃分為以下4種狀態:

  • new - 創建狀態:顧明思議,Java通過new創建了1個線程對象過后,該線程就處于該狀態。
  • runnable- 可履行狀態:也就是指在線程對象調用start()方法落后入的狀態。但需要注意的是該狀態是“可履行狀態”而不是“履行狀態”。也就是說,當1個線程對象調用start方法后,只是意味著它獲得到了CPU的履行資格,其實不代表馬上就會被運行(CPU此時固然可能恰好切換在其它線程上做處理),只有具有了CPU當前履行權的線程才會被履行。
  • non Runnable- 不可履行/阻塞狀態:也就是通過1些方法的控制,使該線程暫時釋放掉了CPU的履行資格的狀態。但此時該線程依然是存在于內存中的。
  • done -退出狀態:簡單的說也就是當線程進入到退出狀態,就意味著它滅亡了,不存在了。Java里通過stop方法可以強迫線程退出,但該方法由于可能引發死鎖,所以是不建議使用的。另外1種進入該狀態的方式,是線程的自然滅亡,也就當1個線程的任務被履行終了以后,就會自然的進入到退出狀態。

以下是Java中1些用于改變線程狀態的方法列表:



Java中創建線程的方式

Java里面創建的線程的方式主要分為:
  • 繼承Thread類,并覆寫run方法。
public class Demo extends Thread{ @Override public void run() { //... } }
  • 實現Runnable接口,并定義run方法:
public class Demo implements Runnable{ @Override public void run() { //... } }
  • 還有1種情況,如果你認為未將線程單獨封裝出來的時候,可以通過匿名內部類來實現。

開發中通常選擇通過實現Runnbale接口的方式創建線程,好處在于:

1.Java中不支持多繼承,所以使用Runnable接口可以免此問題。

2.實現Runnable接口的創建方式,等因而將線程要履行的任務單獨分離了出來,更符合OO要求的封裝性。


多線程的安全隱患

春運將至了,還是先通過1個老話題來看1個多線程并發的例子,來看看多線程可能存在的安全隱患。

package com.tsr.j2seoverstudy.thread; public class TicketDemo { public static void main(String[] args) { Runnable sale = new TicketOffice(); Thread t1 = new Thread(sale, "1號售票窗口"); Thread t2 = new Thread(sale, "2號售票窗口"); t1.start(); t2.start(); } } class TicketOffice implements Runnable { // 某車次的車票存量 private int ticket_num = 10; @Override public void run() { while (true) { if (ticket_num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { } String output = Thread.currentThread().getName() + "售出了" + ticket_num-- + "號票"; System.out.println(output); } else { break; } } } } /* 可能出現以下的輸出結果: 2號售票窗口售出了10號票 1號售票窗口售出了9號票 1號售票窗口售出了8號票 2號售票窗口售出了7號票 1號售票窗口售出了6號票 2號售票窗口售出了5號票 1號售票窗口售出了4號票 2號售票窗口售出了3號票 1號售票窗口售出了2號票 2號售票窗口售出了1號票 1號售票窗口售出了0號票 */
按我們的理想的想法是:兩個售票處共同完成某車次列車的10張車票:坐位號為1號到10號的車票的售票工作。

而根據程序的輸出結果,我們發現的安全隱患是:有坐位號為0號的車票被售出了,買到這張車票的顧客該找誰說理去呢?


我們來分析1下為何會出現這樣的毛病情況,其構成的緣由多是這樣的:

當線程1履行完“1號售票窗口售出了2號票”以后,根據while循環的規則,再1次開始售票工作。

首先判斷while為true,進入到while循環體;接著判斷if語句,此時余票數為1張(也就是只剩下坐位號為1的車票了)。

1大于0,滿足判斷條件,進入到if語句塊當中。此時履行到"Thread.sleep(10)"語句。

OK,當前線程進入到梗塞狀態,暫時失去了Cpu的履行資格。因而Cpu重新切換,開始履行線程2。


因而線程2開始履行線程任務,又是老模樣:while判斷 - if判斷,由于上次線程1判斷后還沒履行售票工作,就被阻塞了。

所以這次if判斷依然為"1>0",滿足判斷條件,繼續履行,又履行到線程休眠語句,因而線程2也進入阻塞狀態。

此時兩個線程暫時都不具有履行資格,但我們指定線程休眠的時間為10毫秒,因而10毫秒后,可能兩個線程都蘇醒了,恢復了Cpu的履行資格。

面對兩個都處于可履行狀態的線程,Cpu又只好隨機選擇1個先履行了。因而Cpu選擇了線程2,線程2恢復履行。

線程2開始做自己上次沒做完的事,因而履行表達式和輸出語句,因而得到輸出信息"2號售票窗口售出了1號票"。


線程2繼續履行while判斷,沒問題。再履行if判斷"0>0",不滿足判斷條件,因而履行到了break語句。

線程2到此退出循環,完成了所有線程任務,因而自然滅亡進入done狀態。

因而現在Cpu的履行權自然就屬于線程1了,線程1也猶如線程21樣,從美夢中醒來,開始上次沒做完的事。

問題就在這里出現了,雖然這個時候,堆內存中寄存的對象成員變量“ticket_num”的值實際上已是0了。

但是!由于上1次線程1已經過了if判斷進入到了if語句塊以內。所以它將直接開始履行表達式,并輸出。

就構成了我們看到的毛病信息:“1號售票窗口售出了0號票”。并且這個時候實際上余票數的值已是“⑴”了。


所以,實際上之所以我們在處理賣票的代碼之前加上讓線程休眠10毫秒的代碼,目的也就是為了摹擬線程安全隱患的問題。

而根據這個例子我們能夠得到的信息就是:之所以多線程并發存在著安全隱患,正是CPU的實際處理方式是在不同線程之間做著隨機的快速切換。

這意味著它其實不會保證當處理1個線程的任務時,1定會履行完該次線程的所有代碼才做切換。而是可能做到1半就切換了。


所以,我們可以歸納線程安全隱患之所以會出現的緣由就是由于:

  • 多個并發線程操作同1個同享數據
  • 操作該同享數據的代碼不止1行,存在多行


解決線程安全隱患的方法 - 同步鎖

既然已了解了線程安全隱患之所以產生,就是由于線程在操作同享數據的途中,其它線程被參與了進來。

那末我們想要解決這1類的安全隱患,自然就是保證在某個線程在履行線程任務的時候,不能讓其余線程來搗亂。

在樣的做法,在Java當中被稱為同步鎖,也就是說給封裝在同步當中的代碼加上1把鎖。

每次只能由1個線程能夠獲得到這把鎖,只有當前持有鎖的線程才能履行同步當中的代碼,其它線程將被拒之門外。


Java中對同步的使用方式通常分為兩種,即:同步代碼塊和同步函數。關鍵字synchronized用以聲明同步。其格式分別為:

//同步代碼塊 synchronized (對象鎖) { //同步代碼 } //同步函數 synchronized void method(){ //同步代碼 }
通過同步我們就能夠解決上面所說的“春節賣票”問題的安全隱患:
package com.tsr.j2seoverstudy.thread; public class TicketDemo { public static void main(String[] args) { Runnable sale = new TicketOffice(); Thread t1 = new Thread(sale, "1號售票窗口"); Thread t2 = new Thread(sale, "2號售票窗口"); t1.start(); t2.start(); } } class TicketOffice implements Runnable { // 某車次的車票存量 private int ticket_num = 10; Object objLock = new Object(); @Override public void run() { while (true) { synchronized (objLock) { if (ticket_num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { } String output = Thread.currentThread().getName() + "售出了" + ticket_num-- + "號票"; System.out.println(output); } else { break; } } } } }
再次運行該代碼,就不會再出現之前的安全隱患。

這正是由于我們通過同步代碼塊,將希望每次只有有1個線程履行的代碼封裝了起來,為它們加上了1把同步鎖(對象)。


同步最需要注意的地方,就是要保證鎖的1致性。這是由于我們說過了:

同步的原理就是鎖,每次當有線程想要訪問同步當中的代碼的時候,只有獲得到該鎖才能履行。

所以如果鎖不能保證是同1把的話,自然也就實現不了所謂的同步了。

可以試著將定義在TicketOffice的成員變量objLock移動定義到run方法當中,就會發現線程安全問題又出現了。

這正是由于,將對象類型變量objLock定義為成員變量,它會隨著該類的對象存儲在堆內存當中,該變量在內存中獨此1份。

而移動到run方法內,則會存儲在棧內存當中,而每個線程都會在棧內存中,單獨開辟1條方法棧。

這樣就等于每一個線程都有1把獨自的鎖,自然也就不是所謂的同步了。


而同步函數的原理實際上與同步代碼塊是相同的,不同的只是將本來包括在同步代碼塊當中的代碼單獨封裝到1個函數中:

private synchronized void saleTicket() { while (true) { if (ticket_num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { } String output = Thread.currentThread().getName() + "售出了" + ticket_num-- + "號票"; System.out.println(output); } else { break; } } }

而另外1點值得說明的是,就是關于不同方式使用的鎖的差別:

同步代碼塊:可使用任1對象鎖。

同步函數:使用this作為鎖。

靜態同步函數:使用該函數所在類的字節碼文件對象作為鎖


死鎖現象

提到同步,就不能不提到與之相干的1個概念:死鎖。

死鎖是指兩個或兩個以上的進程在履行進程中,因爭取資源而釀成的1種相互等待的現象,若無外力作用,它們都將沒法推動下去。

此時稱系統處于死鎖狀態或系統產生了死鎖,這些永久在相互等待的進程稱為死鎖進程。同理,線程也會出現死鎖現象。

<span style="font-family:SimSun;font-size:12px;">package com.tsr.j2seoverstudy.thread; public class DeadLockDemo { public static void main(String[] args) { Queue q1 = new Queue(true); Queue q2 = new Queue(false); Thread t1 = new Thread(q1, "線程1"); Thread t2 = new Thread(q2, "線程2"); t1.start(); t2.start(); } } class MyLocks { public static final Object LOCK_A = new Object(); public static final Object LOCK_B = new Object(); } class Queue implements Runnable { boolean flag; Queue(boolean flag) { this.flag = flag; } @Override public void run() { String threadName = Thread.currentThread().getName(); while (true) { if (flag) { synchronized (MyLocks.LOCK_A) { System.out.println(threadName + "獲得了鎖A"); synchronized (MyLocks.LOCK_B) { System.out.println(threadName + "獲得了鎖B"); } } } else { synchronized (MyLocks.LOCK_B) { System.out.println(threadName + "獲得了鎖B"); synchronized (MyLocks.LOCK_A) { System.out.println(threadName + "獲得了鎖A"); } } } } } }</span>
上面的程序就演示了1個死鎖的現象:

線程1開啟履行后,判斷標記為true,因而先獲得了鎖A,并輸出信息。

此時CPU做切換,線程2開啟履行,判斷標記為false,首先獲得鎖B,并輸出相干信息。

但這時候候不管CPU再怎樣樣切換,程序都已沒法繼續推動了。

由于線程1想要繼續推動必須獲得的資源鎖B現在被線程2持有,反之線程2需要的鎖A被線程1持有。

這正是由于兩個線程由于相互爭取資源而釀成的死鎖現象。

死鎖還是很蛋疼的,1旦出現,程序的調試和查錯修改工作都會變得很麻煩


線程通訊 - 生產者與消費者的例子

關于多線程編程,類似于車站賣票的例子是1種常見的使用處徑。

這類利用途徑通常為:多個線程操作同享數據,并且履行的是同1個動作(線程任務)。

車站售票:多個線程都是操作同1組車票,并且都是履行同1個動作:出售車票。


那末在多線程當中的另外一個經典例子:生產者與消費者,就描寫的是另外一種常見的利用途徑。

多個線程操作同享數據,但是不同的線程之間履行的是不同的動作(線程任務),這就是線程通訊的使用。


不同線程間的通訊應當怎樣樣來完成,其手段是通過Object類當中提供的幾個相干方法:

  • wait():在其他線程調用此對象的notify()方法或notifyAll()方法前,致使當前線程等待。
  • notify():喚醒在此對象監視器上等待的單個(任逐一個)線程。
  • notifyAll():喚醒在此對象監視器上等待的所有線程。

首先,我們可能會思考的1點就是:既然是針對線程之間相互通訊的方法,為何沒有被定義在線程類,反而被定義在了Object類當中。

由于這些方法事實上我們可以視作是線程監視器的方法,監視器其實就是鎖。

我們知道同步中的鎖,可以是任意的對象,那末既然是任1對象調用的方法,自然1定被定義在Object類中。

可以將所有使用同1個同步的線程視作被存儲在同1個線程池當中,而該同步的鎖就是該線程池的監視器。

由該監視器來調度對應線程池內的各個線程,從而到達線程通訊的目的。


接下來就來看生產者與消費者的例子:

1.生產者生產商品;

2.消費者購買商品。

3.可能會同時存在多個生產者與多個消費者。

4.多個生產者中某個生產者生產1件商品,就暫停生產,并在多個消費者中通知1個消費者進行消費;

  消費者消費掉商品后,停止消費,再通知任逐一個生產者進行新的生產工作。

package com.tsr.j2seoverstudy.thread; public class ThreadCommunication { public static void main(String[] args) { Queue q = new Queue(); Customer c = new Customer(q); Producer p = new Producer(q); Thread t1 = new Thread(c, "消費者1-"); Thread t2 = new Thread(c, "消費者2-"); Thread t3 = new Thread(p, "生產者1-"); Thread t4 = new Thread(p, "生產者2-"); t1.start(); t2.start(); t3.start(); t4.start(); } } class Queue { //當前商品數量是不是為0 private boolean isEmpty = true; //生產 public synchronized void put() { String threadName = Thread.currentThread().getName(); //如果生產者線程進入,而現在還有剩余商品 while (!isEmpty) { try { wait();//則該生產者暫時等待,不進行生產 } catch (InterruptedException e) { } } //否則則生產1件商品 isEmpty = false; System.out.println(threadName + "生產了1件商品"); //喚醒阻塞的線程,通知消費者消費 this.notifyAll(); } //消費 public synchronized void take() { String threadName = Thread.currentThread().getName(); //消費者前來消費,如果此時沒有剩余商品 while (isEmpty) { try { wait();//則讓消費者先行等待 } catch (InterruptedException e) { } } //否則則消費掉商品 isEmpty = true; System.out.println(threadName + "消費了1件商品"); //通知生產者沒有商品了,起來繼續生產 this.notifyAll(); } } class Customer implements Runnable { Queue q; Customer(Queue q) { this.q = q; } @Override public void run() { for (int i = 0; i < 5; i++) { q.take(); } } } class Producer implements Runnable { Queue q; Producer(Queue q) { this.q = q; } @Override public void run() { for (int i = 0; i < 5; i++) { q.put(); } } }

這就是對線程通訊1個簡單的利用。而需要記住的是:關于線程的停止與喚醒都必須定義在同步中。

由于我們說過了,關于所謂的線程通訊工作。實際上是通過監視器對象(也就是鎖),來完成對線程的停止或喚醒的操作的。

既然使用的是鎖,那末自然必須被定義在同步中。并且,必須確保相互通訊的線程使用的是同1個鎖。

這是10分重要的,試想1下,如果試圖用線程池A的監視器鎖A去喚醒另外一個線程池B內的某1個線程,這自然是辦不到的。

簡單解釋下,你可能已注意到在上面的例子中,我是直接采取"wait()"和"notifyAll()"的方式來喚醒和阻塞線程的。

那末你應當明白這其實對應于隱式的"this.wait()"與"this.notifyAll()",而同時我們已說過了:

在同步方法中,使用的鎖正是this。也就是說,在線程通訊中,你可以將同步鎖this看作是1個線程池的對象監視器。

當某個線程履行到this.wait(),就代表它在該線程池內阻塞了。而通過this.notify()則可以喚醒阻塞在這個線程池上的線程。



而到了這里,另外一值得1提的1點就是:

Thread類的sleep()方法和Object類的wait()方法都可使當前線程掛起,而它們的不同的地方在于:

1:sleep方法必須線程掛起的時間,超過指定時間,線程將自動從掛起中恢復。而wait方法可以指定時間,也能夠不指定。

2:線程調用sleep方法會釋放Cpu的履行資格(也就是進入到non Runnable狀態),但不會釋放鎖;

   而通過調用wait方法,線程即會釋放cpu的履行資格,同時也會釋放掉鎖。


線程通訊的安全隱患

與之前說過的賣票用例1樣,對線程通訊的通訊也應當謹慎謹慎,否則也可能會引發相干的毛病。常見的問題例如:

1、使用notify而不是notifyAll喚醒線程可能會出現的問題

我在最初接觸多線程的時候,容易這樣斟酌,既然想要到達的目的是:

生產者線程生產1件商品,則喚醒1個消費者線程。消費者進行消費,則喚醒1個生產者線程。

既然notify()方法用于喚醒單個線程,而notifyAll()用于喚醒所有線程,那使用notifyAll不是浪費效力嗎?

后來明白,很惋惜的是,我們要做的是喚醒單個對方線程。而notify沒有這么強大。

它只是隨機的喚醒1個處于阻塞狀態下的線程,所以如果使用notify(),可能會看到以下的毛病情況:


沒錯,操蛋,又出現了坑爹的死鎖。為何出現這樣的情況呢?我們來分析1下:

  • 我們創建的4個線程經調用start方法以后,都進入了可履行狀態,具有CPU履行資格。
  • CPU隨機切換,首先賦予“生產者1”履行權,生產者1開始履行。
  • 生產者1判斷isEmpty為true,履行1次生產任務。當履行notify方法時,當前還沒有任何可以喚醒的阻塞線程。
  • 生產者1繼續while循環,判斷isEmpty為flase。履行wait,因而生產者1進入阻塞狀態。

履行到此,當前處于可履行狀態的線程為:生產者2、消費者1、消費者2

  • CPU在剩下的3個可履行狀態中隨機切換到了生產者2,因而生產者2開始履行。
  • 生產者2判斷isEmpty為false,履行wait方法,因而生產者2也進入到臨時阻塞狀態。

因而,當前處于可履行狀態的線程變成了:消費者1、消費者2

  • CPU繼續隨機切換,此次切換到消費者1開始履行。
  • 消費者1判斷isEmpty為false,因而履行1次消費,修改isEmpty為true。
  • 履行到notify()方法,喚醒任1阻塞狀態的線程,因而喚醒了生產者2。
  • 消費者1繼續while循環,判斷isEmpty為true,因而履行wait,進入阻塞。

到此,當前處于可履行狀態的線程變成了:生產者2、消費者2

  • 一樣的,CPU這次切換到消費者2履行。
  • 消費者2判斷isEmpty為true,因而履行wait,進入阻塞。

好了,處于可履行狀態的線程只剩下:生產者2。

  • 那末,自然現在只能是輪到生產者2履行了。
  • 判斷isEmpty為true,履行1次生產。修改isEmpty為false。
  • 通過notify()方法隨機喚醒了生產者1線程。
  • 再次履行while循環,判斷isEmpty為false后,進入阻塞。

至此,唯1處于可履行狀態的線程變成了:生產者1

  • 生產者1線程開始履行。
  • 判斷isEmpty為false,履行wait進入阻塞。

這下好了,4個線程都進入了阻塞狀態,而不是滅亡狀態。自然的,死鎖了。


2、使用if而不是使用while判斷isEmpty可能出現的問題

如果使用if而不是while對isEmpty進行判斷,可能會出現的毛病為:

1、不同的生產者連續生產了多件商品,但消費者只消費掉其中1件。

2、1個生產者生產了1件商品以后,有多個消費者進行連續消費。

出現這樣的安全問題是由于if的判斷機制釀成的:通過if來判斷標記,只會履行1次判斷。

所以可能會致使不該運行的線程運行了,從而出現數據毛病的情況。

這類問題的出現也就是與我們上面說的“售票處售出0號票”的毛病類似。


JDK1.5以后的新特性

我們前面已說到了,關于生產者與消費者的問題中。

我們的目的是,每當1個線程履行終了1次任務后,只喚醒單1的對方線程。

而在JDK1.5之前,為了不死鎖的產生,我們不能不使用notifyAll()來喚醒線程。

而這樣做有1個缺點就在于:每次都要喚醒所有處于阻塞的線程,自然就會致使效力下降。


在JDK1.5以后,,Java提供了新的工具用于解決此類問題,就是:Lock和Condition接口。

簡答的說,就是對將本來的同步鎖synchronized與對象監視器進行了封裝,分別對應于于Lock及Condition。

并且,重要的是相對1.5之前,新的工具具有更靈活及更廣泛的操作。


1、Lock的使用及注意事項

1、通過Lock lock =  new ReentrantLock();獲得1個Lock對象。

2、通過成員方法lock(),用于對代碼進行同步管理。

3、通過成員方法unlock(),用于同步代碼履行終了后,釋放鎖對象。

4、由于不管在同步代碼的履行進程中是不是出現異常,最后都必須釋放該鎖,否則可能會致使死鎖現象的產生。所以通常在使用lock時,都會遵守以下格式:

     lock.lock();

     try{

     {

      // 同步代碼....

     }finally{

    lock.unlock();

       }

     }

2、對象監視器Condition的使用及注意事項

1、可以通過Lock對象使用成員方法newCondition()來獲得1個新的監視器對象。

2、Condition分別使用await();signal();signalAll()來替換本來Object類當中的wait();notify();及notifyAll()方法。

3、同1個Lock對象可以具有多個不同的Condition對象。


請注意1個很關鍵的特性:同1個Lock對象可以具有多個不同的Condition對象

也就是說:通過此特性,我們可以獲得多個Condition對象,將操作不同線程任務的線程分別寄存在不同的Condition對象當中。

例如在前面所說的生產者消費者例子當中,我們就能夠生成兩組監視器,1組監視生產者線程,1組監視消費者線程。

從而到達我們想要的每次只喚醒對方線程而不喚醒本方線程的目的,修改后的例子代碼以下:

import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadCommunication { public static void main(String[] args) { Queue q = new Queue(); Customer c = new Customer(q); Producer p = new Producer(q); Thread t1 = new Thread(c,"消費者1-"); Thread t2 = new Thread(c,"消費者2-"); Thread t3 = new Thread(p,"生產者1-"); Thread t4 = new Thread(p,"生產者2-"); t1.start(); t2.start(); t3.start(); t4.start(); } } class Queue { private int goodsTotal; private boolean isEmpty = true; final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); public void put() { String threadName = Thread.currentThread().getName(); lock.lock(); try{ while (!isEmpty) { try { notFull.await(); } catch (InterruptedException e) { } } goodsTotal ++; System.out.println(threadName + "生產了1件商品"); isEmpty = false; notEmpty.signal(); }finally{ lock.unlock(); } } public synchronized void take() { String threadName = Thread.currentThread().getName(); lock.lock(); try{ while (isEmpty) { try { notEmpty.await(); } catch (InterruptedException e) { } } goodsTotal --; System.out.println(threadName + "消費了1件商品"); isEmpty = true; notFull.signal(); }finally{ lock.unlock(); } } } class Customer implements Runnable { Queue q; Customer(Queue q) { this.q = q; } @Override public void run() { while (true) { q.take(); } } } class Producer implements Runnable { Queue q; Producer(Queue q) { this.q = q; } @Override public void run() { while (true) { q.put(); } } }


線程的經常使用方法

最后,看1下1些關于線程的經常使用方法。

1、線程的中斷工作

1、通常使用自然中斷的做法,也就是當某個線程的線程任務履行結束以后,該線程就會自然終結。

2、通過標記控制。如果線程任務中存在循環(通常都有),那末,可以在循環中使用標記,通過標記來控制線程的中斷。


2、interrupt()方法:中斷線程

我們知道sleep及wait等方法都可使線程進入阻塞狀態。所以可能你在程序通過使用標記的方式來控制線程的中斷,但由于進程中線程墮入了凍結(掛起/阻塞)狀態,這時候通過標記將沒法正常的控制線程中斷。這時候,就能夠通過interrupt方法來中斷線程的凍結狀態,強迫恢復到運行狀態中來,讓線程具有cpu的履行資格。但是由于此方法具有強迫性,所以會引發InterruptedException,所以要記得處理異常。

3、setDaemon()方法:將該線程標記為守護線程或用戶線程。

所謂守護線程,可以理解為后臺線程。對應的,我們在程序中開辟的線程都可以視為前臺線程,在Java中,當所有的前臺線程都履行結束以后,后臺線程也將隨之結束。

例如:你在某個程序中開辟兩個線程,1個用于接收輸入,1個用于控制輸出。由于只有當有輸入存在時,才會存在輸出。這時候就能夠通過setDaemon將輸出線程設置為守護線程。這樣當輸入線程中斷結束時,輸出線程就會隨之自動中斷,而沒必要再人為控制中斷。


4、控制線程優先級

所謂控制線程優先級,是指我們可以通過設置線程的優先級來控制線程被CPU運行到的概率,線程的優先級越高,被CPU運行的幾率越大。

通過setPriority()與getPriority()方法可以分別設置和獲得某個線程的優先級。Java中線程的優先級取值范圍為:1⑴0

Thread類中使用MAX_PRIORITY(10),NORM_PRIORITY(5),MIN_PRIORITY(1)3個常量代表最經常使用的線程優先級值。


5、join()方法

線程使用join方法,意味著該線程申請加入履行,所以通常如果要臨時加入1個線程,可使用join()方法。并且,當履行到join方法以后,其余線程將等待使用該方法的線程履行完線程任務以后,再繼續履行。


6、yiled()方法

暫停正在履行的線程對象,并履行其他線程。


生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 精品欧美 | 欧美一级在线全免费 | 亚洲精品福利 | 中文字幕在线视频观看 | 亚洲欧洲eeea在线观看 | 亚洲色图亚洲色图 | 欧美色图天堂网 | 亚洲视频中文字幕 | 羞羞影院免费观看网址在线 | 精品免费久久 | 亚洲伊人成综合网 | 网友偷自拍原创区 | 黄视频在线观看www 黄网站大全 | 欧美性猛交xxxxx按摩欧美 | 操你.com| 亚洲综合综合在线 | 成人福利网址 | h网站免费在线观看 | 91久久精品一区二区 | 中文字幕av在线 | 日本三级s级在线播放 | 免费爽视频 | www.亚洲日本 | 最近中文字幕2019免费版日本 | 国产亚洲精品久久久久久久 | 午夜dj视频在线视频中文 | 波多野结衣中文一区 | 春色视频网站 | 欧美jizzhd精品欧美巨大 | 亚洲影院手机版777点击进入影院 | 国产激情一区二区三区四区 | 最近手机版免费中文字幕 | 久久久综合久久 | 一级一级一级毛片免费毛片 | 国产高清在线不卡 | 免费视频不卡一区二区三区 | 99久久精品免费看国产免费软件 | 亚洲另类春色校园小说 | 91久久综合九色综合欧美98 | h在线看 | 欧美1页|