




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
第Java線程間共享與協(xié)作詳細(xì)介紹目錄線程的共享synchronized內(nèi)置鎖錯誤的加鎖和原因分析volatile,最輕量的同步機(jī)制ThreadLocal與Synchonized的比較ThreadLocal的使用實現(xiàn)解析內(nèi)存泄漏的現(xiàn)象分析引發(fā)的內(nèi)存泄漏分析錯誤使用ThreadLocal導(dǎo)致線程不安全線程間的協(xié)作等待/通知機(jī)制等待和通知的標(biāo)準(zhǔn)范式notify和notifyAll應(yīng)該用誰等待超時模式實現(xiàn)一個連接池面試題
線程的共享
synchronized內(nèi)置鎖
Java支持多個線程同時訪問一個對象或者對象的成員變量,關(guān)鍵字synchronized可以修飾方法或者以同步塊的形式來進(jìn)行使用,它主要確保多個線程在同一個時刻,只能有一個線程處于方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性,又稱為內(nèi)置鎖機(jī)制。對象鎖和類鎖:對象鎖是用于對象實例方法,或者一個對象實例上的,類鎖是用于類的靜態(tài)方法或者一個類的class對象上的。我們知道,類的對象實例可以有很多個,但是每個類只有一個class對象,所以不同對象實例的對象鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點必須注意的是,其實類鎖只是一個概念上的東西,并不是真實存在的,類鎖其實鎖的是每個類的對應(yīng)的class對象。類鎖和對象鎖之間也是互不干擾的。
代碼示例:
*類說明:synchronized關(guān)鍵字的使用方法
publicclassSynTest{
privatelongcount=0;
privateObjectobj=newObject();//作為一個鎖
publiclonggetCount(){
returncount;
publicvoidsetCount(longcount){
this.count=count;
/*用在同步塊上*/
publicvoidincCount(){
synchronized(obj){
count++;
/*用在方法上*/
publicsynchronizedvoidincCount2(){
count++;
/*用在同步塊上,但是鎖的是當(dāng)前類的對象實例*/
publicvoidincCount3(){
synchronized(this){
count++;
//線程
privatestaticclassCountextendsThread{
privateSynTestsimplOper;
publicCount(SynTestsimplOper){
this.simplOper=simplOper;
@Override
publicvoidrun(){
for(inti=0;i10000;i++){
simplOper.incCount();//count=count+10000
publicstaticvoidmain(String[]args)throwsInterruptedException{
SynTestsimplOper=newSynTest();
//啟動兩個線程
Countcount1=newCount(simplOper);
Countcount2=newCount(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
System.out.println(simplOper.count);//20000
}
/**
*類說明:鎖的實例不一樣,也是可以并行的
publicclassDiffInstance{
privatestaticclassInstanceSynimplementsRunnable{
privateDiffInstancediffInstance;
publicInstanceSyn(DiffInstancediffInstance){
this.diffInstance=diffInstance;
@Override
publicvoidrun(){
System.out.println("TestInstanceisrunning..."+diffInstance);
diffInstance.instance();
privatestaticclassInstance2SynimplementsRunnable{
privateDiffInstancediffInstance;
publicInstance2Syn(DiffInstancediffInstance){
this.diffInstance=diffInstance;
@Override
publicvoidrun(){
System.out.println("TestInstance2isrunning..."+diffInstance);
diffInstance.instance2();
privatesynchronizedvoidinstance(){
SleepTools.second(3);
System.out.println("synInstanceisgoing..."+this.toString());
SleepTools.second(3);
System.out.println("synInstanceended"+this.toString());
privatesynchronizedvoidinstance2(){
SleepTools.second(3);
System.out.println("synInstance2isgoing..."+this.toString());
SleepTools.second(3);
System.out.println("synInstance2ended"+this.toString());
publicstaticvoidmain(String[]args){
DiffInstanceinstance1=newDiffInstance();
Threadt3=newThread(newInstance2Syn(instance1));
DiffInstanceinstance2=newDiffInstance();
Threadt4=newThread(newInstanceSyn(instance1));
//先執(zhí)行完一個才會執(zhí)行另外一個
t3.start();
t4.start();
SleepTools.second(1);
}
/**
*類說明:演示實例鎖和類鎖是不同的,兩者可以并行
publicclassInstanceAndClass{
privatestaticclassSynClassextendsThread{
@Override
publicvoidrun(){
System.out.println("TestClassisrunning...");
synClass();
privatestaticclassInstanceSynimplementsRunnable{
privateInstanceAndClassSynClassAndInstance;
publicInstanceSyn(InstanceAndClassSynClassAndInstance){
this.SynClassAndInstance=SynClassAndInstance;
@Override
publicvoidrun(){
System.out.println("TestInstanceisrunning..."+SynClassAndInstance);
SynClassAndInstance.instance();
privatesynchronizedvoidinstance(){
SleepTools.second(1);
System.out.println("synInstanceisgoing..."+this.toString());
SleepTools.second(1);
System.out.println("synInstanceended"+this.toString());
privatestaticsynchronizedvoidsynClass(){
SleepTools.second(1);
System.out.println("synClassgoing...");
SleepTools.second(1);
System.out.println("synClassend");
publicstaticvoidmain(String[]args){
InstanceAndClasssynClassAndInstance=newInstanceAndClass();
Threadt1=newSynClass();
Threadt2=newThread(newInstanceSyn(synClassAndInstance));
t2.start();
SleepTools.second(1);
t1.start();
}
/**
*類說明:類鎖和鎖static變量也是不同的可以并行
publicclassStaticAndClass{
privatestaticclassSynClassextendsThread{
@Override
publicvoidrun(){
System.out.println(currentThread().getName()
+":SynClassisrunning...");
synClass();
privatestaticclassSynStaticextendsThread{
@Override
publicvoidrun(){
System.out.println(currentThread().getName()
+"SynStaticisrunning...");
synStatic();
privatestaticsynchronizedvoidsynClass(){
System.out.println(Thread.currentThread().getName()
+"synClassgoing...");
SleepTools.second(1);
System.out.println(Thread.currentThread().getName()
+"synClassend");
privatestaticObjectobj=newObject();
privatestaticvoidsynStatic(){
synchronized(obj){
System.out.println(Thread.currentThread().getName()
+"synStaticgoing...");
SleepTools.second(1);
System.out.println(Thread.currentThread().getName()
+"synStaticend");
publicstaticvoidmain(String[]args){
StaticAndClasssynClassAndInstance=newStaticAndClass();
Threadt1=newSynClass();
//Threadt2=newSynStatic();
Threadt2=newSynClass();
t2.start();
SleepTools.second(1);
t1.start();
}
錯誤的加鎖和原因分析
原因:雖然我們對i進(jìn)行了加鎖,但是
但是當(dāng)我們反編譯這個類的class文件后,可以看到i++實際是,
本質(zhì)上是返回了一個新的Integer對象。也就是每個線程實際加鎖的是不同的Integer對象。
volatile,最輕量的同步機(jī)制
volatile保證了不同線程對這個變量進(jìn)行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
不加volatile時,子線程無法感知主線程修改了ready的值,從而不會退出循環(huán),而加了volatile后,子線程可以感知主線程修改了ready的值,迅速退出循環(huán)。
/**
*類說明:演示Volatile的提供的可見性
publicclassVolatileCase{
privatevolatilestaticbooleanready;
privatestaticintnumber;
privatestaticclassPrintThreadextendsThread{
@Override
publicvoidrun(){
System.out.println("PrintThreadisrunning.......");
while(!ready);//無限循環(huán)
System.out.println("number="+number);
publicstaticvoidmain(String[]args){
newPrintThread().start();
SleepTools.second(1);
number=51;//如果沒有加volatile關(guān)鍵字則主線程都結(jié)束了也沒有打印number的值,加了關(guān)鍵值后打印出來的值就是主線程修改的值
ready=true;
SleepTools.second(5);
System.out.println("mainisended!");
}
但是volatile不能保證數(shù)據(jù)在多個線程下同時寫時的線程安全。
/**
*類說明:
publicclassNotSafe{
privatevolatilelongcount=0;
publiclonggetCount(){
returncount;
publicvoidsetCount(longcount){
this.count=count;
//count進(jìn)行累加
publicvoidincCount(){
count++;
//線程
privatestaticclassCountextendsThread{
privateNotSafesimplOper;
publicCount(NotSafesimplOper){
this.simplOper=simplOper;
@Override
publicvoidrun(){
for(inti=0;i10000;i++){
simplOper.incCount();
publicstaticvoidmain(String[]args)throwsInterruptedException{
NotSafesimplOper=newNotSafe();
//啟動兩個線程
Countcount1=newCount(simplOper);
Countcount2=newCount(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
System.out.println(simplOper.count);//20000
}
volatile最適用的場景:一個線程寫,多個線程讀。
ThreadLocal
與Synchonized的比較
ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問??墒荰hreadLocal與synchronized有本質(zhì)的差別。synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時該僅僅能被一個線程訪問。而ThreadLocal為每個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并非同一個對象,這樣就隔離了多個線程對數(shù)據(jù)的數(shù)據(jù)共享。Spring的事務(wù)就借助了ThreadLocal類。Spring會從數(shù)據(jù)庫連接池中獲得一個connection,然會把connection放進(jìn)ThreadLocal中,也就和線程綁定了,事務(wù)需要提交或者回滾,只要從ThreadLocal中拿到connection進(jìn)行操作。為何Spring的事務(wù)要借助ThreadLocal類?
以JDBC為例,正常的事務(wù)代碼可能如下:
dbc=newDataBaseConnection();//第1行
Connectioncon=dbc.getConnection();//第2行
con.setAutoCommit(false);////第3行
con.executeUpdate(...);//第4行
con.executeUpdate(...);//第5行
con.executeUpdate(...);//第6行
mit();////第7行
上述代碼,可以分成三個部分:事務(wù)準(zhǔn)備階段:第1~3行業(yè)務(wù)處理階段:第4~6行事務(wù)提交階段:第7行可以很明顯的看到,不管我們開啟事務(wù)還是執(zhí)行具體的sql都需要一個具體的數(shù)據(jù)庫連接。現(xiàn)在我們開發(fā)應(yīng)用一般都采用三層結(jié)構(gòu),如果我們控制事務(wù)的代碼都放在DAO(DataAccessObject)對象中,在DAO對象的每個方法當(dāng)中去打開事務(wù)和關(guān)閉事務(wù),當(dāng)Service對象在調(diào)用DAO時,如果只調(diào)用一個DAO,那我們這樣實現(xiàn)則效果不錯,但往往我們的Service會調(diào)用一系列的DAO對數(shù)據(jù)庫進(jìn)行多次操作,那么,這個時候我們就無法控制事務(wù)的邊界了,因為實際應(yīng)用當(dāng)中,我們的Service調(diào)用的DAO的個數(shù)是不確定的,可根據(jù)需求而變化,而且還可能出現(xiàn)Service調(diào)用Service的情況。
如果不使用ThreadLocal,代碼大概就會是這個樣子:
但是需要注意一個問題,如何讓三個DAO使用同一個數(shù)據(jù)源連接呢?我們就必須為每個DAO傳遞同一個數(shù)據(jù)庫連接,要么就是在DAO實例化的時候作為構(gòu)造方法的參數(shù)傳遞,要么在每個DAO的實例方法中作為方法的參數(shù)傳遞。這兩種方式無疑對我們的Spring框架或者開發(fā)人員來說都不合適。為了讓這個數(shù)據(jù)庫連接可以跨階段傳遞,又不顯示的進(jìn)行參數(shù)傳遞,就必須使用別的辦法。Web容器中,每個完整的請求周期會由一個線程來處理。因此,如果我們能將一些參數(shù)綁定到線程的話,就可以實現(xiàn)在軟件架構(gòu)中跨層次的參數(shù)共享(是隱式的共享)。而JAVA中恰好提供了綁定的方法--使用ThreadLocal。結(jié)合使用Spring里的IOC和AOP,就可以很好的解決這一點。只要將一個數(shù)據(jù)庫連接放入ThreadLocal中,當(dāng)前線程執(zhí)行時只要有使用數(shù)據(jù)庫連接的地方就從ThreadLocal獲得就行了。
ThreadLocal的使用
ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:voidset(Objectvalue)設(shè)置當(dāng)前線程的線程局部變量的值。publicObjectget()該方法返回當(dāng)前線程所對應(yīng)的線程局部變量。publicvoidremove()將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK5.0新增的方法。需要指出的是,當(dāng)線程結(jié)束后,對應(yīng)該線程的局部變量將自動被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。protectedObjectinitialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設(shè)計的。這個方法是一個延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實現(xiàn)直接返回一個null。
publicfinalstaticThreadLocalRESOURCE=newThreadLocal();RESOURCE代表一個能夠存放String類型的ThreadLocal對象。此時不論什么一個線程能夠并發(fā)訪問這個變量,對它進(jìn)行寫入、讀取操作,都是線程安全的。
代碼示例:
/**
*類說明:演示ThreadLocal的使用
publicclassUseThreadLocal{
privatestaticThreadLocalIntegerintLocal
=newThreadLocalInteger(){
@Override
protectedIntegerinitialValue(){
return1;
privatestaticThreadLocalStringstringThreadLocal;
*運行3個線程
publicvoidStartThreadArray(){
Thread[]runs=newThread[3];
for(inti=0;iruns.length;i++){
runs[i]=newThread(newTestThread(i));
for(inti=0;iruns.length;i++){
runs[i].start();
*類說明:測試線程,線程的工作是將ThreadLocal變量的值變化,并寫回,看看線程之間是否會互相影響
publicstaticclassTestThreadimplementsRunnable{
intid;
publicTestThread(intid){
this.id=id;
publicvoidrun(){
System.out.println(Thread.currentThread().getName()+":start");
Integers=intLocal.get();
s=s+id;
intLocal.set(s);
System.out.println(Thread.currentThread().getName()
+":"+intLocal.get());
//intLocal.remove();
publicstaticvoidmain(String[]args){
UseThreadLocaltest=newUseThreadLocal();
test.StartThreadArray();
}
/**
*類說明:
publicclassNoThreadLocal{
staticIntegercount=newInteger(1);
*運行3個線程
publicvoidStartThreadArray(){
Thread[]runs=newThread[3];
for(inti=0;iruns.length;i++){
runs[i]=newThread(newTestTask(i));
for(inti=0;iruns.length;i++){
runs[i].start();
*類說明:
publicstaticclassTestTaskimplementsRunnable{
intid;
publicTestTask(intid){
this.id=id;
publicvoidrun(){
System.out.println(Thread.currentThread().getName()+":start");
count=count+id;
System.out.println(Thread.currentThread().getName()+":"
+count);
publicstaticvoidmain(String[]args){
NoThreadLocaltest=newNoThreadLocal();
test.StartThreadArray();
}
實現(xiàn)解析
上面先取到當(dāng)前線程,然后調(diào)用getMap方法獲取對應(yīng)的ThreadLocalMap,ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類,然后Thread類中有一個這樣類型成員,所以getMap是直接返回Thread的成員。
看下ThreadLocal的內(nèi)部類ThreadLocalMap源碼:
可以看到有個Entry內(nèi)部靜態(tài)類,它繼承了WeakReference,總之它記錄了兩個信息,一個是ThreadLocal類型,一個是Object類型的值。getEntry方法則是獲取某個ThreadLocal對應(yīng)的值,set方法就是更新或賦值相應(yīng)的ThreadLocal對應(yīng)的值。
回顧我們的get方法,其實就是拿到每個線程獨有的ThreadLocalMap,然后再用ThreadLocal的當(dāng)前實例,拿到Map中的相應(yīng)的Entry,然后就可以拿到相應(yīng)的值返回出去。當(dāng)然,如果Map為空,還會先進(jìn)行map的創(chuàng)建,初始化等工作。
引發(fā)的內(nèi)存泄漏分析
引用Objecto=newObject();這個o,我們可以稱之為對象引用,而newObject()我們可以稱之為在內(nèi)存中產(chǎn)生了一個對象實例。
當(dāng)寫下o=null時,只是表示o不再指向堆中object的對象實例,不代表這個對象實例不存在了。
強(qiáng)引用就是指在程序代碼之中普遍存在的,類似Objectobj=newObject()這類的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象實例。
軟引用是用來描述一些還有用但并非必需的對象。對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象實例列進(jìn)回收范圍之中進(jìn)行第二次回收。如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。在JDK1.2之后,提供了SoftReference類來實現(xiàn)軟引用。
弱引用也是用來描述非必需對象的,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象實例只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象實例。在JDK1.2之后,提供了WeakReference類來實現(xiàn)弱引用。
虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。一個對象實例是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象實例被收集器回收時收到一個系統(tǒng)通知。在JDK1.2之后,提供了PhantomReference類來實現(xiàn)虛引用。
內(nèi)存泄漏的現(xiàn)象
將堆內(nèi)存大小設(shè)置為-Xmx256m我們啟用一個線程池,大小固定為5個線程
finalstaticThreadPoolExecutorpoolExecutor
=newThreadPoolExecutor(5,5,
TimeUnit.MINUTES,
newLinkedBlockingQueue());
場景1,首先任務(wù)中不執(zhí)行任何有意義的代碼,當(dāng)所有的任務(wù)提交執(zhí)行完成后,可以看見,我們這個應(yīng)用的內(nèi)存占用基本上為25M左右
場景2,然后我們只簡單的在每個任務(wù)中new出一個數(shù)組,執(zhí)行完成后我們可以看見,內(nèi)存占用基本和場景1同
場景3,當(dāng)我們啟用了ThreadLocal以后:
執(zhí)行完成后我們可以看見,內(nèi)存占用變?yōu)榱?00M左右場景4,于是,我們加入一行代碼,再執(zhí)行,看看內(nèi)存情況:
可以看見,內(nèi)存占用基本和場景1同。這就充分說明,場景3,當(dāng)我們啟用了ThreadLocal以后確實發(fā)生了內(nèi)存泄漏。
分析
根據(jù)我們前面對ThreadLocal的分析,我們可以知道每個Thread維護(hù)一個ThreadLocalMap,這個映射表的key是ThreadLocal實例本身,value是真正需要存儲的Object,也就是說ThreadLocal本身并不存儲值,它只是作為一個key來讓線程從ThreadLocalMap獲取value。仔細(xì)觀察ThreadLocalMap,這個map是使用ThreadLocal的弱引用作為Key的,弱引用的對象在GC時會被回收。
因此使用了ThreadLocal后,引用鏈如圖所示:
圖中的虛線表示弱引用。這樣,當(dāng)把threadlocal變量置為null以后,沒有任何強(qiáng)引用指向threadlocal實例,所以threadlocal將會被gc回收。這樣一來,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會一直存在一條強(qiáng)引用鏈:ThreadRef-Thread-ThreaLocalMap-Entry-value,而這塊value永遠(yuǎn)不會被訪問到了,所以存在著內(nèi)存泄露。只有當(dāng)前thread結(jié)束以后,currentthread就不會存在棧中,強(qiáng)引用斷開,CurrentThread、Mapvalue將全部被GC回收。最好的做法是不在需要使用ThreadLocal變量后,都調(diào)用它的remove()方法,清除數(shù)據(jù)。所以回到我們前面的實驗場景,場景3中,雖然線程池里面的任務(wù)執(zhí)行完畢了,但是線程池里面的5個線程會一直存在直到JVM退出,我們set了線程的localVariable變量后沒有調(diào)用localVariable.remove()方法,導(dǎo)致線程池里面的5個線程的threadLocals變量里面的newLocalVariable()實例沒有被釋放。其實考察ThreadLocal的實現(xiàn),我們可以看見,無論是get()、set()在某些時候,調(diào)用了expungeStaleEntry方法用來清除Entry中Key為null的Value,但是這是不及時的,也不是每次都會執(zhí)行的,所以一些情況下還是會發(fā)生內(nèi)存泄露。只有remove()方法中顯式調(diào)用了expungeStaleEntry方法。從表面上看內(nèi)存泄漏的根源在于使用了弱引用,但是另一個問題也同樣值得
思考:為什么使用弱引用而不是強(qiáng)引用?
下面我們分兩種情況討論:
key使用強(qiáng)引用:引用ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用,如果沒有手動刪除,ThreadLocal的對象實例不會被回收,導(dǎo)致Entry內(nèi)存泄漏。
key使用弱引用:引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal的對象實例也會被回收。value在下一次ThreadLocalMap調(diào)用set,get,remove都有機(jī)會被回收。
比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動刪除對應(yīng)key,都會導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障。因此,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應(yīng)key就會導(dǎo)致內(nèi)存泄漏,而不是因為弱引用。
總結(jié)JVM利用設(shè)置ThreadLocalMap的Key為弱引用,來避免內(nèi)存泄露。JVM利用調(diào)用remove、get、set方法的時候,回收弱引用。當(dāng)ThreadLocal存儲很多Key為null的Entry的時候,而不再去調(diào)用remove、get、set方法,那么將導(dǎo)致內(nèi)存泄漏。使用線程池+ThreadLocal時要小心,因為這種情況下,線程是一直在不斷的重復(fù)運行的,從而也就造成了value可能造成累積的情況。
/**
*類說明:ThreadLocal造成的內(nèi)存泄漏演示
publicclassThreadLocalOOM{
privatestaticfinalintTASK_LOOP_SIZE=500;
finalstaticThreadPoolExecutorpoolExecutor
=newThreadPoolExecutor(5,5,
TimeUnit.MINUTES,
newLinkedBlockingQueue());
staticclassLocalVariable{
privatebyte[]a=newbyte[1024*1024*5];/*5M大小的數(shù)組*/
finalstaticThreadLocalLocalVariablelocalVariable
=newThreadLocal();
publicstaticvoidmain(String[]args)throwsInterruptedException{
Objecto=newObject();
/*5*5=25*/
for(inti=0;iTASK_LOOP_SIZE;++i){
poolExecutor.execute(newRunnable(){
publicvoidrun(){
//localVariable.set(newLocalVariable());
newLocalVariable();
System.out.println("uselocalvaraible");
//localVariable.remove();
Thread.sleep(100);
System.out.println("poolexecuteover");
}
錯誤使用ThreadLocal導(dǎo)致線程不安全
/**
*類說明:ThreadLocal的線程不安全演示
publicclassThreadLocalUnsafeimplementsRunnable{
publicstaticNumbernumber=newNumber(0);
publicvoidrun(){
//每個線程計數(shù)加一
number.setNum(number.getNum()+1);
//將其存儲到ThreadLocal中
value.set(number);
SleepTools.ms(2);
//輸出num值
System.out.println(Thread.currentThread().getName()+"="+value.get().getNum());
publicstaticThreadLocalNumbervalue=newThreadLocalNumber(){
publicstaticvoidmain(String[]args){
for(inti=0;ii++){
newThread(newThreadLocalUnsafe()).start();
privatestaticclassNumber{
publicNumber(intnum){
this.num=num;
privateintnum;
publicintgetNum(){
returnnum;
publicvoidsetNum(intnum){
this.num=num;
@Override
publicStringtoString(){
return"Number[num="+num+"]";
}
為什么每個線程都輸出5?難道他們沒有獨自保存自己的Number副本嗎?為什么其他線程還是能夠修改這個值?仔細(xì)考察ThreadLocal和Thead的代碼,我們發(fā)現(xiàn)ThreadLocalMap中保存的其實是對象的一個引用,這樣的話,當(dāng)有其他線程對這個引用指向的對象實例做修改時,其實也同時影響了所有的線程持有的對象引用所指向的同一個對象實例。這也就是為什么上面的程序為什么會輸出一樣的結(jié)果:5個線程中保存的是同一Number對象的引用,在線程睡眠的時候,其他線程將num變量進(jìn)行了修改,而修改的對象Number的實例是同一份,因此它們最終輸出的結(jié)果是相同的。而上面的程序要正常的工作,應(yīng)該的用法是讓每個線程中的ThreadLocal都應(yīng)該持有一個新的Number對象。
線程間的協(xié)作
線程之間相互配合,完成某項工作,比如:一個線程修改了一個對象的值,而另一個線程感知到了變化,然后進(jìn)行相應(yīng)的操作,整個過程開始于一個線程,而最終執(zhí)行又是另一個線程。前者是生產(chǎn)者,后者就是消費者,這種模式隔離了做什么(what)和怎么做(How),簡單的辦法是讓消費者線程不斷地循環(huán)檢查變量是否符合預(yù)期在while循環(huán)中設(shè)置不滿足的條件,如果條件滿足則退出while循環(huán),從而完成消費者的工作。卻存在如下問題:1)難以確保及時性。2)難以降低開銷。如果降低睡眠的時間,比如休眠1毫秒,這樣消費者能更加迅速地發(fā)現(xiàn)條件變化,但是卻可能消耗更多的處理器資源,造成了無端的浪費。
等待/通知機(jī)制
是指一個線程A調(diào)用了對象O的wait()方法進(jìn)入等待狀態(tài),而另一個線程B調(diào)用了對象O的notify()或者notifyAll()方法,線程A收到通知后從對象O的wait()方法返回,進(jìn)而執(zhí)行后續(xù)操作。上述兩個線程通過對象O來完成交互,而對象上的wait()和notify/notifyAll()的關(guān)系就如同開關(guān)信號一樣,用來完成等待方和通知方之間的交互工作。
notify():通知一個在對象上等待的線程,使其從wait方法返回,而返回的前提是該線程獲取到了對象的鎖,沒有獲得鎖的線程重新進(jìn)入WAITING狀態(tài)。
notifyAll():通知所有等待在該對象上的線程
wait()調(diào)用該方法的線程進(jìn)入WAITING狀態(tài),只有等待另外線程的通知或被中斷才會返回.需要注意,調(diào)用wait()方法后,會釋放對象的鎖
wait(long)超時等待一段時間,這里的參數(shù)時間是毫秒,也就是等待長達(dá)n毫秒,如果沒有通知就超時返回
wait(long,int)對于超時時間更細(xì)粒度的控制,可以達(dá)到納秒
等待和通知的標(biāo)準(zhǔn)范式
等待方遵循如下原則:
1)獲取對象的鎖。2)如果條件不滿足,那么調(diào)用對象的wait()方法,被通知后仍要檢查條件。3)條件滿足則執(zhí)行對應(yīng)的邏輯。
通知方遵循如下原則:
1)獲得對象的鎖。2)改變條件。3)通知所有等待在對象上的線程。
在調(diào)用wait()、notify()系列方法之前,線程必須要獲得該對象的對象級別鎖,即只能在同步方法或同步塊中調(diào)用wait()方法、notify()系列方法,進(jìn)入wait()方法后,當(dāng)前線程釋放鎖,在從wait()返回前,線程與其他線程競爭重新獲得鎖,執(zhí)行notify()系列方法的線程退出調(diào)用了notifyAll的synchronized代碼塊的時候后,他們就會去競爭。如果其中一個線程獲得了該對象鎖,它就會繼續(xù)往下執(zhí)行,在它退出synchronized代碼塊,釋放鎖后,其他的已經(jīng)被喚醒的線程將會繼續(xù)競爭獲取該鎖,一直進(jìn)行下去,直到所有被喚醒的線程都執(zhí)行完畢。
notify和notifyAll應(yīng)該用誰
盡可能用notifyall(),謹(jǐn)慎使用notify(),因為notify()只會喚醒一個線程,我們無法確保被喚醒的這個線程一定就是我們需要喚醒的線程
代碼示例:
/**
*類說明:快遞實體類
publicclassExpress{
publicfinalstaticStringCITY="ShangHai";
privateintkm;/*快遞運輸里程數(shù)*/
privateStringsite;/*快遞到達(dá)地點*/
publicExpress(){
publicExpress(intkm,Stringsite){
this.km=km;
this.site=site;
/*變化公里數(shù),然后通知處于wait狀態(tài)并需要處理公里數(shù)的線程進(jìn)行業(yè)務(wù)處理*/
publicsynchronizedvoidchangeKm(){
this.km=101;
notify();
/*變化地點,然后通知處于wait狀態(tài)并需要處理地點的線程進(jìn)行業(yè)務(wù)處理*/
publicsynchronizedvoidchangeSite(){
this.site="BeiJing";
notifyAll();
/*線程等待公里的變化*/
publicsynchronizedvoidwaitKm(){
while(this.km100){
try{
wait();
System.out.println("CheckSitethread["
+Thread.currentThread().getId()
+"]isbenotified");
}catch(InterruptedExceptione){
e.printStackTrace();
System.out.println("theKmis"+this.km+",Iwillchangedb");
/*線程等待目的地的變化*/
publicsynchronizedvoidwaitSite(){
while(this.site.equals(CITY)){//快遞到達(dá)目的地
try{
wait();
System.out.println("CheckSitethread["+Thread.currentThread().getId()
+"]isbenotified");
}catch(InterruptedExceptione){
e.printStackTrace();
System.out.println("thesiteis"+this.site+",Iwillcalluser");
}
/**
*類說明:測試wait/notify/notifyAll
publicclassTestWN{
privatestaticExpressexpress=newExpress(0,Express.CITY);
/*檢查里程數(shù)變化的線程,不滿足條件,線程一直等待*/
privatestaticclassCheckKmextendsThread{
@Override
publicvoidrun(){
express.waitKm();
/*檢查地點變化的線程,不滿足條件,線程一直等待*/
privatestaticclassCheckSiteextendsThread{
@Override
publicvoidrun(){
express.waitSite();
publicstaticvoidmain(String[]args)throwsInterruptedException{
for(inti=0;ii++){
newCheckSite().start();
for(inti=0;ii++){
newCheckKm().start();
Thread.sleep(1000);
express.changeKm();//快遞地點變化
}
等待超時模式實現(xiàn)一個連接池
調(diào)用場景:調(diào)用一個方法時等待一段時間(一般來說是給定一個時間段),如果該方法能夠在給定的時間段之內(nèi)得到結(jié)果,那么將結(jié)果立刻返回,反之,超時返回默認(rèn)結(jié)果假設(shè)等待時間段是T,那么可以推斷出在當(dāng)前時間now+T之后就會超時等待持續(xù)時間:REMAINING=T。超時時間:FUTURE=now+T。
/**
*類說明:連接池的實現(xiàn)
publicclassDBPool{
/*容器,存放連接*/
privatestaticLinkedListConnectionpool=newLinkedListConnection
/*限制了池的大小=20*/
publicDBPool(intinitialSize){
if(initialSize0){
for(inti=0;iinitialSize;i++){
pool.addLast(SqlConnectImpl.fetchConnection());
/*釋放連接,通知其他的等待連接的線程*/
publicvoidreleaseConnection(Connectionconnection){
if(connection!=null){
synchronized(pool){
pool.addLast(connection);
//通知其他等待連接的線程
pool.notifyAll();
/*獲取*/
//在mills內(nèi)無法獲取到連接,將會返回null1S
publicConnectionfetchConnection(longmills)
throwsInterruptedException{
synchronized(pool){
//永不超時
if(mills=0){
while(pool.isEmpty()){
pool.wait();
returnpool.removeFirst();
}else{
/*超時時刻*/
longfuture=System.currentTimeMillis()+mills;
/*等待時長*/
longremaining=mills;
while(pool.isEmpty()remaining0){
pool.wait(remaining);
/*喚醒一次,重新計算等待時長*/
remaining=future-System.currentTimeMillis();
Connectionconnection=null;
if(!pool.isEmpty()){
connection=
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 把握考試動向2025年信息系統(tǒng)項目管理師試題及答案
- 公共政策評估的關(guān)鍵指標(biāo)試題及答案
- 2024年年4K超高清資金籌措計劃書代可行性研究報告
- 軟件設(shè)計師考試多層面提升方案試題及答案
- 政治輿論對決策的影響軌跡試題及答案
- 影響公共衛(wèi)生政策的政治因素分析試題及答案
- 更有效地學(xué)習(xí)西方政治考試試題及答案
- 軟件設(shè)計師考試中的時間分配技巧試題及答案
- 西方國家政策的社會影響分析試題及答案
- 社會心理學(xué)在公共政策分析中的應(yīng)用試題及答案
- 光伏施工安全培訓(xùn)
- 國企崗位筆試題目及答案
- 社工招錄考試試題及答案
- 餐廳廚房5S管理
- 變配電運行值班員(220kV及以下)高級技師-機(jī)考題庫(導(dǎo)出版)
- DB11-T 2398-2025 水利工程巡視檢查作業(yè)規(guī)范
- 2025春季學(xué)期國開電大本科《人文英語3》一平臺在線形考綜合測試(形考任務(wù))試題及答案
- 《人工智能安全導(dǎo)論》 課件 第七章 人工智能在聯(lián)邦學(xué)習(xí)領(lǐng)域
- 員工電動車管理制度
- 百葉窗施工工藝方案 組織設(shè)計
- 授權(quán)審批管理制度
評論
0/150
提交評論