摘要:線程需要避免竟態(tài),死鎖以及很多其他共享狀態(tài)的并發(fā)性問(wèn)題。用戶線程在前臺(tái),守護(hù)線程在后臺(tái)運(yùn)行,為其他前臺(tái)線程提供服務(wù)。當(dāng)所有前臺(tái)線程都退出時(shí),守護(hù)線程就會(huì)退出。線程阻塞等待獲取某個(gè)對(duì)象鎖的訪問(wèn)權(quán)限。
1、多線程介紹 多線程優(yōu)點(diǎn)
資源利用率好
程序設(shè)計(jì)簡(jiǎn)單
服務(wù)器響應(yīng)更快
多線程缺點(diǎn)設(shè)計(jì)更復(fù)雜
上下文切換的開(kāi)銷
增加資源消耗
線程需要內(nèi)存維護(hù)本地的堆棧,同時(shí)需要操作系統(tǒng)資源管理線程。
并發(fā)系統(tǒng)可以有多種并發(fā)模型,不同的并發(fā)模型在處理任務(wù)時(shí),線程間的協(xié)作和交互的方式也不同。
并行工作者委托者將任務(wù)分配到不同的現(xiàn)場(chǎng)去執(zhí)行,每個(gè)工作者完成整個(gè)任務(wù)。工作者們并行運(yùn)作在不同的線程上,甚至可能在不同的CPU上。如圖所示:
優(yōu)點(diǎn):很容易理解和使用。
缺點(diǎn):
共享狀態(tài)會(huì)很復(fù)雜
共享的工作者經(jīng)常需要訪問(wèn)一些共享數(shù)據(jù),無(wú)論是內(nèi)存中的或者共享的數(shù)據(jù)庫(kù)中的。
在并行工作者模型中,線程需要以某種方式存取共享數(shù)據(jù),以確保某個(gè)線程的修改能夠?qū)ζ渌€程可見(jiàn)。線程需要避免竟態(tài),死鎖以及很多其他共享狀態(tài)的并發(fā)性問(wèn)題。
無(wú)狀態(tài)的工作者
共享狀態(tài)能夠被系統(tǒng)中得其他線程修改。所以工作者在每次需要的時(shí)候必須重讀狀態(tài),以確保每次都能訪問(wèn)到最新的副本,不管共享狀態(tài)是保存在內(nèi)存中的還是在外部數(shù)據(jù)庫(kù)中。工作者無(wú)法在內(nèi)部保存這個(gè)狀態(tài)(但是每次需要的時(shí)候可以重讀)稱為無(wú)狀態(tài)的。
每次都重讀需要的數(shù)據(jù),將會(huì)導(dǎo)致速度變慢,特別是狀態(tài)保存在外部數(shù)據(jù)庫(kù)中的時(shí)候。
任務(wù)順序是不確定的
作業(yè)執(zhí)行順序是不確定的。無(wú)法保證哪個(gè)作業(yè)最先或者最后被執(zhí)行。
類似于工廠中生產(chǎn)線上的工人們那樣組織工作者。每個(gè)工作者只負(fù)責(zé)作業(yè)中的部分工作。當(dāng)完成了自己的這部分工作時(shí)工作者會(huì)將作業(yè)轉(zhuǎn)發(fā)給下一個(gè)工作者。每個(gè)工作者在自己的線程中運(yùn)行,并且不會(huì)和其他工作者共享狀態(tài)。有時(shí)也被成為無(wú)共享并行模型。
通常使用非阻塞的IO來(lái)設(shè)計(jì)使用流水線并發(fā)模型的系統(tǒng)。非阻塞IO就是,一旦某個(gè)工作者開(kāi)始一個(gè)IO操作的時(shí)候(比如讀取文件或從網(wǎng)絡(luò)連接中讀取數(shù)據(jù)),這個(gè)工作者不會(huì)一直等待IO操作的結(jié)束。IO操作速度很慢,所以等待IO操作結(jié)束很浪費(fèi)CPU時(shí)間。此時(shí)CPU可以做一些其他事情。當(dāng)IO操作完成的時(shí)候,IO操作的結(jié)果(比如讀出的數(shù)據(jù)或者數(shù)據(jù)寫完的狀態(tài))被傳遞給下一個(gè)工作者。
在實(shí)際過(guò)程中,可能會(huì)是這樣:
也可能是這樣:
當(dāng)然還會(huì)有更復(fù)雜的設(shè)計(jì),……
缺點(diǎn): 代碼編寫復(fù)雜,追蹤某個(gè)作業(yè)到底被什么代碼執(zhí)行難度較大。
優(yōu)點(diǎn):
無(wú)需共享的狀態(tài)
工作者之間無(wú)需共享狀態(tài),無(wú)需考慮所有因并發(fā)訪問(wèn)共享對(duì)象而產(chǎn)生的并發(fā)性問(wèn)題,基本上是一個(gè)單線程的實(shí)現(xiàn)。
有狀態(tài)的工作者
當(dāng)工作者知道了沒(méi)有其他線程可以修改它們的數(shù)據(jù),工作者可以變成有狀態(tài)的。對(duì)于有狀態(tài),是指,可以在內(nèi)存中保存它們需要操作的數(shù)據(jù),只需在最后將更改寫回到外部存儲(chǔ)系統(tǒng)。因此,有狀態(tài)的工作者通常比無(wú)狀態(tài)的工作者具有更高的性能。
較好的硬件整合(Hardware Conformity)
當(dāng)能確定代碼只在單線程模式下執(zhí)行的時(shí)候,通常能夠創(chuàng)建更優(yōu)化的數(shù)據(jù)結(jié)構(gòu)和算法。單線程有狀態(tài)的工作者能夠在內(nèi)存中緩存數(shù)據(jù),訪問(wèn)緩存的數(shù)據(jù)變得更快。
合理的作業(yè)順序
基于流水線并發(fā)模型實(shí)現(xiàn)的并發(fā)系統(tǒng),在某種程度上是有可能保證作業(yè)的順序的。作業(yè)的有序性使得它更容易地推出系統(tǒng)在某個(gè)特定時(shí)間點(diǎn)的狀態(tài)。更進(jìn)一步,你可以將所有到達(dá)的作業(yè)寫入到日志中去。一旦這個(gè)系統(tǒng)的某一部分掛掉了,該日志就可以用來(lái)重頭開(kāi)始重建系統(tǒng)當(dāng)時(shí)的狀態(tài)。按照特定的順序?qū)⒆鳂I(yè)寫入日志,并按這個(gè)順序作為有保障的作業(yè)順序。
Actors在Actor模型中每個(gè)工作者被稱為actor。Actor之間可以直接異步地發(fā)送和處理消息。Actor可以被用來(lái)實(shí)現(xiàn)一個(gè)或多個(gè)像前文描述的那樣的作業(yè)處理流水線。下圖給出了Actor模型:
工作者之間不直接進(jìn)行通信。相反,它們?cè)诓煌耐ǖ乐邪l(fā)布自己的消息(事件)。其他工作者們可以在這些通道上監(jiān)聽(tīng)消息,發(fā)送者無(wú)需知道誰(shuí)在監(jiān)聽(tīng)。下圖給出了Channel模型:
channel模型對(duì)于來(lái)說(shuō)似乎更加靈活。一個(gè)工作者無(wú)需知道誰(shuí)在后面的流水線上處理作業(yè)。只需知道作業(yè)(或消息等)需要轉(zhuǎn)發(fā)給哪個(gè)通道。通道上的監(jiān)聽(tīng)者可以隨意訂閱或者取消訂閱,并不會(huì)影響向這個(gè)通道發(fā)送消息的工作者。這使得工作者之間具有松散的耦合。
3、實(shí)現(xiàn)多線程方式多線程實(shí)現(xiàn)方法有兩種:
繼承Thread類
public class MyThread extends Thread { public void run(){ System.out.println("MyThread running"); } } //調(diào)用 MyThread myThread = new MyThread(); myTread.start();
實(shí)現(xiàn)Runnble接口
public class MyRunnable implements Runnable { public void run(){ System.out.println("MyRunnable running"); } } //調(diào)用 Thread thread = new Thread(new MyRunnable()); thread.start();
實(shí)現(xiàn)Runnble接口比Thread類的優(yōu)勢(shì):
可以避免Java單繼承帶來(lái)的局限
增強(qiáng)程序健壯性,能夠被多個(gè)線程共享,代碼和數(shù)據(jù)是獨(dú)立的
適合多個(gè)相同程序代碼的線程區(qū)處理同一資源
Thread中,start和run的區(qū)別:run是在當(dāng)前線程運(yùn)行,start是開(kāi)辟新的線程運(yùn)行!所以一般情況下使用的是start!
執(zhí)行完run()方法后,或在run()方法中return,線程便自然消亡。
當(dāng)一個(gè)線程運(yùn)行時(shí),另一個(gè)線程可以調(diào)用對(duì)應(yīng)的 Thread 對(duì)象的 interrupt()方法來(lái)中斷它,該方法只是在目標(biāo)線程中設(shè)置一個(gè)標(biāo)志,表示它已經(jīng)被中斷,并立即返回。這里需要注意的是,如果只是單純的調(diào)用 interrupt()方法,線程并沒(méi)有實(shí)際被中斷,會(huì)繼續(xù)往下執(zhí)行。
sleep()方法的實(shí)現(xiàn)檢查到休眠線程被中斷,它會(huì)相當(dāng)友好地終止線程,并拋出 InterruptedException 異常。
public class SleepInterrupt extends Object implements Runnable{ public void run(){ try{ System.out.println("in run() - about to sleep for 20 seconds"); Thread.sleep(20000); System.out.println("in run() - woke up"); }catch(InterruptedException e){ System.out.println("in run() - interrupted while sleeping"); //處理完中斷異常后,返回到run()方法人口, //如果沒(méi)有return,線程不會(huì)實(shí)際被中斷,它會(huì)繼續(xù)打印下面的信息 return; } System.out.println("in run() - leaving normally"); } public static void main(String[] args) { SleepInterrupt si = new SleepInterrupt(); Thread t = new Thread(si); t.start(); //主線程休眠2秒,從而確保剛才啟動(dòng)的線程有機(jī)會(huì)執(zhí)行一段時(shí)間 try { Thread.sleep(2000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("in main() - interrupting other thread"); //中斷線程t t.interrupt(); System.out.println("in main() - leaving"); } }
如果將 catch 塊中的 return 語(yǔ)句注釋掉,則線程在拋出異常后,會(huì)繼續(xù)往下執(zhí)行,而不會(huì)被中斷,從而會(huì)打印出leaving normally信息。
待決中斷另外一種情況,如果線程在調(diào)用 sleep()方法前被中斷,那么該中斷稱為待決中斷,它會(huì)在剛調(diào)用 sleep()方法時(shí),立即拋出 InterruptedException 異常。
public class PendingInterrupt extends Object { public static void main(String[] args){ //如果輸入了參數(shù),則在mian線程中中斷當(dāng)前線程(亦即main線程) if( args.length > 0 ){ Thread.currentThread().interrupt(); } //獲取當(dāng)前時(shí)間 long startTime = System.currentTimeMillis(); try{ Thread.sleep(2000); System.out.println("was NOT interrupted"); }catch(InterruptedException x){ System.out.println("was interrupted"); } //計(jì)算中間代碼執(zhí)行的時(shí)間 System.out.println("elapsedTime=" + ( System.currentTimeMillis() - startTime)); } }
這種模式下,main 線程中斷它自身。除了將中斷標(biāo)志(它是 Thread 的內(nèi)部標(biāo)志)設(shè)置為 true 外,沒(méi)有其他任何影響。線程被中斷了,但 main 線程仍然運(yùn)行,main 線程繼續(xù)監(jiān)視實(shí)時(shí)時(shí)鐘,并進(jìn)入 try 塊,一旦調(diào)用 sleep()方法,它就會(huì)注意到待決中斷的存在,并拋出 InterruptException。
中斷狀態(tài)判斷
isInterrupted()方法判斷是否中斷
Thread.interrupted()方法判斷中斷狀態(tài)
join & yieldjoin 方法用線程對(duì)象調(diào)用,如果在一個(gè)線程 A 中調(diào)用另一個(gè)線程 B 的 join 方法,線程 A 將會(huì)等待線程 B 執(zhí)行完畢后再執(zhí)行。
yield 可以直接用 Thread 類調(diào)用,yield 讓出 CPU 執(zhí)行權(quán)給同等級(jí)的線程,如果沒(méi)有相同級(jí)別的線程在等待 CPU 的執(zhí)行權(quán),則該線程繼續(xù)執(zhí)行。
守護(hù)線程Java有兩類線程:UserThread(用戶線程)、Daemon Thread(守護(hù)線程)。
用戶線程在前臺(tái),守護(hù)線程在后臺(tái)運(yùn)行,為其他前臺(tái)線程提供服務(wù)。當(dāng)所有前臺(tái)線程都退出時(shí),守護(hù)線程就會(huì)退出。如果有前臺(tái)線程仍然存活,守護(hù)線程就不會(huì)退出。
守護(hù)線程并非只有虛擬機(jī)內(nèi)部提供,用戶可以使用Thread.setDaemon(true)方法設(shè)置為當(dāng)前線程為守護(hù)線程。
setDaemon(true)必須在調(diào)用的線程的start()方法之前設(shè)置,否則會(huì)拋出異常。
在守護(hù)線程中產(chǎn)生的新線程也是守護(hù)線程
線程阻塞4、線程安全線程在以下四種狀態(tài)下會(huì)產(chǎn)生阻塞:
執(zhí)行Thread.sleep()
當(dāng)線程遇見(jiàn)wait()語(yǔ)句,它會(huì)一直阻塞到接到通知notify()
線程阻塞與不同的I/O的方式有多種。例:InputStream的read方法,一直阻塞到從流中讀取一個(gè)字節(jié)的數(shù)據(jù)為知。
線程阻塞等待獲取某個(gè)對(duì)象鎖的訪問(wèn)權(quán)限。
競(jìng)態(tài)條件 & 臨界區(qū)定義:當(dāng)多個(gè)線程訪問(wèn)某個(gè)類時(shí),這個(gè)類始終都能表現(xiàn)出正確的行為,那么這個(gè)類就是線程安全的!
當(dāng)兩個(gè)線程競(jìng)爭(zhēng)同一資源時(shí),如果對(duì)資源的訪問(wèn)順序敏感,就稱存在競(jìng)態(tài)條件。
導(dǎo)致競(jìng)態(tài)條件發(fā)生的代碼區(qū)稱作:臨界區(qū)。
下例中add()方法就是一個(gè)臨界區(qū),它會(huì)產(chǎn)生競(jìng)態(tài)條件。在臨界區(qū)中使用適當(dāng)?shù)耐骄涂梢员苊飧?jìng)態(tài)條件。
public class Counter { protected long count = 0; public void add(long value){ this.count = this.count + value; } }數(shù)據(jù)安全
線程逃逸規(guī)則:如果一個(gè)資源的創(chuàng)建,使用,銷毀都在同一個(gè)線程內(nèi)完成,且永遠(yuǎn)不會(huì)脫離該線程的控制,則該資源的使用就是線程安全的。
屬性 | 描述 | 是否線程安全 |
---|---|---|
局部變量 | 在棧中,不會(huì)被線程共享 | 線程安全 |
局部對(duì)象 | 引用所指的對(duì)象都存在共享堆中,對(duì)象不會(huì)被其它方法獲得,也不會(huì)被非局部變量引用到 | 線程安全 |
對(duì)象成員 | 多個(gè)線程執(zhí)行讀操作,或者每個(gè)線程的對(duì)象都相互獨(dú)立 | 線程安全 |
局部對(duì)象 | 對(duì)象會(huì)被其它方法獲得,或者被全局變量引用到 | 線程非安全 |
對(duì)象成員 | 存儲(chǔ)在堆上。若多個(gè)線程同時(shí)更新同一個(gè)對(duì)象的同一個(gè)成員 | 線程非安全 |
當(dāng)多個(gè)線程同時(shí)訪問(wèn)同一個(gè)資源,并且其中的一個(gè)或者多個(gè)線程對(duì)這個(gè)資源進(jìn)行了寫操作,才會(huì)產(chǎn)生競(jìng)態(tài)條件。多個(gè)線程同時(shí)讀同一個(gè)資源不會(huì)產(chǎn)生競(jìng)態(tài)條件。
我們可以通過(guò)創(chuàng)建不可變的共享對(duì)象來(lái)保證對(duì)象在線程間共享時(shí)不會(huì)被修改,從而實(shí)現(xiàn)線程安全,如下所示:
public class ImmutableValue{ private int value = 0; public ImmutableValue(int value){ this.value = value; } public int getValue(){ return this.value; } }
如果非要對(duì)ImmutableValue進(jìn)行操作的話,可以創(chuàng)建新的實(shí)例進(jìn)行隔離:
public class ImmutableValue{ private int value = 0; public ImmutableValue(int value){ this.value = value; } public int getValue(){ return this.value; } //創(chuàng)建一個(gè)新的實(shí)例 public ImmutableValue add(int valueToAdd){ return new ImmutableValue(this.value + valueToAdd); } }
ImmutableValue可以看做是線程安全的,但是如果別的類引用了ImmutableValue,就不能保證線程安全了。如下所示:
public void Calculator{ private ImmutableValue currentValue = null; public ImmutableValue getValue(){ return currentValue; } public void setValue(ImmutableValue newValue){ this.currentValue = newValue; } public void add(int newValue){ this.currentValue = this.currentValue.add(newValue); } }
即使Calculator類內(nèi)部使用了一個(gè)不可變對(duì)象,但Calculator類本身還是可變的,因此Calculator類不是線程安全的。換句話說(shuō):ImmutableValue類是線程安全的,但使用它的類不是。
5、同步(synchronized)當(dāng)多個(gè)線程訪問(wèn)某個(gè)狀態(tài)變量,并且有線程執(zhí)行寫入操作時(shí),必須采用同步機(jī)制來(lái)協(xié)同這些線程對(duì)變量的訪問(wèn)。
Java的主要同步機(jī)制有:
synchronized關(guān)鍵字
volatile類型變量
顯示鎖
原子變量
無(wú)論是同步方法,還是同步塊都是只針對(duì)同一個(gè)對(duì)象的多線程而言的,只有同一個(gè)對(duì)象產(chǎn)生的多線程,才會(huì)考慮到同步方法或者是同步塊。
實(shí)例方法Java實(shí)例方法同步是同步在對(duì)象上。這樣,每個(gè)方法同步都同步在方法所屬的實(shí)例。只有一個(gè)線程能夠在實(shí)例方法同步塊中運(yùn)行。如果有多個(gè)實(shí)例存在,那么一個(gè)線程一次可以在一個(gè)實(shí)例同步塊中執(zhí)行操作。一個(gè)實(shí)例一個(gè)線程。
public synchronized void add(int value){ this.count += value; }靜態(tài)方法同步
靜態(tài)方法的同步是指同步在該方法所在的類對(duì)象上。因?yàn)樵贘ava虛擬機(jī)中一個(gè)類只能對(duì)應(yīng)一個(gè)類對(duì)象,所以同時(shí)只允許一個(gè)線程執(zhí)行同一個(gè)類中的靜態(tài)同步方法。
對(duì)于不同類中的靜態(tài)同步方法,一個(gè)線程可以執(zhí)行每個(gè)類中的靜態(tài)同步方法而無(wú)需等待。不管類中的那個(gè)靜態(tài)同步方法是否被調(diào)用,一個(gè)類只能由一個(gè)線程同時(shí)執(zhí)行。
public static synchronized void add(int value){ count += value; }實(shí)例方法中的同步塊
有時(shí)你不需要同步整個(gè)方法,而是同步方法中的一部分。
public void add(int value){ synchronized(this){ this.count += value; } }
示例使用Java同步塊構(gòu)造器來(lái)標(biāo)記一塊代碼是同步的。該代碼在執(zhí)行時(shí)和同步方法一樣。在上例中,使用了“this”,即為調(diào)用add方法的實(shí)例本身。在同步構(gòu)造器中用括號(hào)括起來(lái)的對(duì)象叫做監(jiān)視器對(duì)象。
靜態(tài)方法中的同步塊和上面類似,下面是兩個(gè)靜態(tài)方法同步的例子。這些方法同步在該方法所屬的類對(duì)象上。
public class MyClass { public static synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } public static void log2(String msg1, String msg2){ synchronized(MyClass.class){ log.writeln(msg1); log.writeln(msg2); } } }
這兩個(gè)方法不允許同時(shí)被線程訪問(wèn)。
如果第二個(gè)同步塊不是同步在MyClass.class這個(gè)對(duì)象上。那么這兩個(gè)方法可以同時(shí)被線程訪問(wèn)。
線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號(hào)。另一方面,線程通信使線程能夠等待其他線程的信號(hào)。
通過(guò)共享對(duì)象通信線程間發(fā)送信號(hào)的一個(gè)簡(jiǎn)單方式是在共享對(duì)象的變量里設(shè)置信號(hào)值。
public class MySignal{ protected boolean hasDataToProcess = false; public synchronized boolean hasDataToProcess(){ return this.hasDataToProcess; } public synchronized void setHasDataToProcess(boolean hasData){ this.hasDataToProcess = hasData; } }
線程A在一個(gè)同步塊里設(shè)置boolean型成員變量hasDataToProcess為true,線程B也在同步塊里讀取hasDataToProcess這個(gè)成員變量。
線程A和B必須獲得指向一個(gè)MySignal共享實(shí)例的引用,以便進(jìn)行通信。如果它們持有的引用指向不同的MySingal實(shí)例,那么彼此將不能檢測(cè)到對(duì)方的信號(hào)。
線程B運(yùn)行在一個(gè)循環(huán)里,等待線程A的一個(gè)可執(zhí)行的信號(hào)。
protected MySignal sharedSignal = ... ... while(!sharedSignal.hasDataToProcess()){ //do nothing... busy waiting }wait(),notify()和notifyAll()
除非忙等待的時(shí)間特別短,否則會(huì)浪費(fèi)CPU資源。合理的做法:讓等待線程進(jìn)入睡眠或者非運(yùn)行狀態(tài),直到它接收到它等待的信號(hào)。
java.lang.Object 類定義了三個(gè)方法,wait()、notify()和notifyAll()來(lái)實(shí)現(xiàn)這個(gè)等待機(jī)制。
一個(gè)線程一旦調(diào)用了任意對(duì)象的wait()方法,就會(huì)變?yōu)榉沁\(yùn)行狀態(tài),直到另一個(gè)線程調(diào)用了同一個(gè)對(duì)象的notify()方法。
為了調(diào)用wait()或者notify(),線程必須先獲得那個(gè)對(duì)象的鎖。也就是說(shuō),線程必須在同步塊里調(diào)用wait()或者notify()。
在wait()/notify()機(jī)制中,不要使用全局對(duì)象,字符串常量等。應(yīng)該使用對(duì)應(yīng)唯一的對(duì)象
public class MonitorObject{ } public class MyWaitNotify{ MonitorObject myMonitorObject = new MonitorObject(); public void doWait(){ synchronized(myMonitorObject){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } } public void doNotify(){ synchronized(myMonitorObject){ myMonitorObject.notify(); } } }
不管是等待線程還是喚醒線程都在同步塊里調(diào)用wait()和notify()。這是強(qiáng)制性的!一個(gè)線程如果沒(méi)有持有對(duì)象鎖,將不能調(diào)用wait(),notify()或者notifyAll()。否則,會(huì)拋出IllegalMonitorStateException異常。
一旦線程調(diào)用了wait()方法,它就釋放了所持有的監(jiān)視器對(duì)象上的鎖。這將允許其他線程也可以調(diào)用wait()或者notify()。
被喚醒的線程必須重新獲得監(jiān)視器對(duì)象的鎖,才可以退出wait()的方法調(diào)用,因?yàn)閣ait方法調(diào)用運(yùn)行在同步塊里面。如果多個(gè)線程被notifyAll()喚醒,那么在同一時(shí)刻將只有一個(gè)線程可以退出wait()方法,因?yàn)槊總€(gè)線程在退出wait()前必須獲得監(jiān)視器對(duì)象的鎖。
丟失信號(hào)notify()和notifyAll()方法不會(huì)保存調(diào)用它們的方法,如果方法被調(diào)用時(shí),沒(méi)有線程處于等待狀態(tài)。通知信號(hào)過(guò)后便丟棄了。因此,如果一個(gè)線程先于被通知線程調(diào)用wait()前調(diào)用了notify(),等待的線程將錯(cuò)過(guò)這個(gè)信號(hào)。在某些情況下,這可能使線程錯(cuò)過(guò)了喚醒信號(hào),永遠(yuǎn)在等待不再醒來(lái)。
為了避免丟失信號(hào),必須把它們保存在信號(hào)類里。在MyWaitNotify的例子中,通知信號(hào)應(yīng)被存儲(chǔ)在MyWaitNotify實(shí)例的一個(gè)成員變量里。
public class MyWaitNotify2{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ if(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
在上述例子中,doNotify()方法在調(diào)用notify()前把wasSignalled變量設(shè)為true。同時(shí),留意doWait()方法在調(diào)用wait()前會(huì)檢查wasSignalled變量。
為了避免信號(hào)丟失,用一個(gè)變量來(lái)保存是否被通知過(guò)。在notify前,設(shè)置自己已經(jīng)被通知過(guò)。在wait后,設(shè)置自己沒(méi)有被通知過(guò),需要等待通知。。
假喚醒線程有可能在沒(méi)有調(diào)用過(guò)notify()和notifyAll()的情況下醒來(lái)。這就是所謂的假喚醒(spurious wakeups)。等待線程即使沒(méi)有收到正確的信號(hào),也能夠執(zhí)行后續(xù)的操作。
為了防止假喚醒,保存信號(hào)的成員變量將在一個(gè)while循環(huán)里接受檢查,而不是在if表達(dá)式里。這樣的一個(gè)while循環(huán)叫做自旋鎖。
public class MyWaitNotify3{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
如果等待線程沒(méi)有收到信號(hào)就喚醒,wasSignalled變量將變?yōu)閒alse,while循環(huán)會(huì)再執(zhí)行一次,促使醒來(lái)的線程回到等待狀態(tài)。
7、TheadLocal目前的JVM實(shí)現(xiàn)自旋會(huì)消耗CPU,如果長(zhǎng)時(shí)間不調(diào)用doNotify方法,doWait方法會(huì)一直自旋,CPU會(huì)消耗太大。
ThreadLocal類創(chuàng)建的變量只被同一個(gè)線程進(jìn)行讀和寫操作。因此,盡管有兩個(gè)線程同時(shí)執(zhí)行一段相同的代碼,而且這段代碼又有一個(gè)指向同一個(gè)ThreadLocal變量的引用,但是這兩個(gè)線程依然不能看到彼此的ThreadLocal變量域。
//創(chuàng)建一個(gè)ThreadLocal變量:每個(gè)線程僅需要實(shí)例化一次即可。 //每個(gè)線程只能看到私有的ThreadLocal實(shí)例,不同的線程在給ThreadLocal對(duì)象設(shè)置不同的值,也不能看到彼此的修改。 private ThreadLocal myThreadLocal = new ThreadLocal(); //設(shè)置、獲取數(shù)據(jù) myThreadLocal.set("A thread local value"); String threadLocalValue = (String) myThreadLocal.get(); //創(chuàng)建泛型對(duì)象 private ThreadLocal myThreadLocal1 = new ThreadLocal(); myThreadLocal1.set("Hello ThreadLocal"); String threadLocalValues = myThreadLocal.get();
InheritableThreadLocal類是ThreadLocal的子類。為了解決ThreadLocal實(shí)例內(nèi)部每個(gè)線程都只能看到自己的私有值,所以InheritableThreadLocal允許一個(gè)線程創(chuàng)建的所有子線程訪問(wèn)其父線程的值。
引用
1、并發(fā)編程網(wǎng)-Java并發(fā)性和多線程
2、蘭亭風(fēng)雨專欄
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/66891.html
摘要:目錄介紹問(wèn)題匯總具體問(wèn)題好消息博客筆記大匯總年月到至今,包括基礎(chǔ)及深入知識(shí)點(diǎn),技術(shù)博客,學(xué)習(xí)筆記等等,還包括平時(shí)開(kāi)發(fā)中遇到的匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善開(kāi)源的文件是格式的同時(shí)也開(kāi)源了生活博客,從年 目錄介紹 00.Java問(wèn)題匯總 01.具體問(wèn)題 好消息 博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技...
摘要:我的學(xué)習(xí)筆記匯總標(biāo)簽筆記分為兩大部分和筆記內(nèi)容主要是對(duì)一些基礎(chǔ)特性和編程細(xì)節(jié)進(jìn)行總結(jié)整理,適合了解基礎(chǔ)語(yǔ)法,想進(jìn)一步深入學(xué)習(xí)的人如果覺(jué)得不錯(cuò),請(qǐng)給,這也是對(duì)我的鼓勵(lì),有什么意見(jiàn)歡迎留言反饋目錄基礎(chǔ)鞏固筆記反射基礎(chǔ)鞏固筆記泛型基礎(chǔ)鞏 我的java&javaweb學(xué)習(xí)筆記(匯總) 標(biāo)簽: java [TOC] 筆記分為兩大部分:javase和javaweb javase javawe...
摘要:對(duì)象頭的另外一部分是類型指針,即對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例。并不是所有的虛擬機(jī)實(shí)現(xiàn)都必須在對(duì)象數(shù)據(jù)上保留類型指針,換句話說(shuō),查找對(duì)象的元數(shù)據(jù)信息并不一定要經(jīng)過(guò)對(duì)象本身,這點(diǎn)將在節(jié)討論。 目錄介紹 1.關(guān)于int和Integer的問(wèn)題區(qū)別分析 2.Integer的值緩存的原理 2.1 Java 5 中引入緩存特性 2.2 Intege...
摘要:如問(wèn)到是否使用某框架,實(shí)際是是問(wèn)該框架的使用場(chǎng)景,有什么特點(diǎn),和同類可框架對(duì)比一系列的問(wèn)題。這兩個(gè)方向的區(qū)分點(diǎn)在于工作方向的側(cè)重點(diǎn)不同。 [TOC] 這是一份來(lái)自嗶哩嗶哩的Java面試Java面試 32個(gè)核心必考點(diǎn)完全解析(完) 課程預(yù)習(xí) 1.1 課程內(nèi)容分為三個(gè)模塊 基礎(chǔ)模塊: 技術(shù)崗位與面試 計(jì)算機(jī)基礎(chǔ) JVM原理 多線程 設(shè)計(jì)模式 數(shù)據(jù)結(jié)構(gòu)與算法 應(yīng)用模塊: 常用工具集 ...
摘要:分區(qū)函數(shù)返回一個(gè)布爾值,這意味著得到的分組的鍵類型是,于是它最多可以分為兩組是一組,是一組。當(dāng)遍歷到流中第個(gè)元素時(shí),這個(gè)函數(shù)執(zhí)行時(shí)會(huì)有兩個(gè)參數(shù)保存歸約結(jié)果的累加器已收集了流中的前個(gè)項(xiàng)目,還有第個(gè)元素本身。 一、收集器簡(jiǎn)介 把列表中的交易按貨幣分組: Map transactionsByCurrencies = transactions.stream().collect(groupi...
摘要:前言是一個(gè)開(kāi)源的壓力測(cè)試工具,常用于應(yīng)用壓力測(cè)試,本文針使用其對(duì)接口進(jìn)行并發(fā)性能測(cè)試,做筆記以備忘。 前言 Jmetter是一個(gè)開(kāi)源的壓力測(cè)試工具,常用于Web應(yīng)用壓力測(cè)試,本文針使用其對(duì)api接口進(jìn)行并發(fā)性能測(cè)試,做筆記以備忘。 目錄 一、下載和安裝 1. Jmetter下載 2.下載并安...
閱讀 1654·2019-08-30 13:04
閱讀 2205·2019-08-30 12:59
閱讀 1764·2019-08-29 18:34
閱讀 1857·2019-08-29 17:31
閱讀 1255·2019-08-29 15:42
閱讀 3530·2019-08-29 15:37
閱讀 2857·2019-08-29 13:45
閱讀 2771·2019-08-26 13:57