本篇內(nèi)容主要講解“java線程封閉的方法是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“java線程封閉的方法是什么”吧!
創(chuàng)新互聯(lián)主要從事網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)南昌縣,十多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):028-86922220
線程封閉的概念
訪問共享變量時(shí),通常要使用同步,所以避免使用同步的方法就是減少共享數(shù)據(jù)的使用,這種技術(shù)就是線程封閉。
實(shí)現(xiàn)線程封閉的方法
1:ad-hoc線程封閉
這是完全靠實(shí)現(xiàn)者控制的線程封閉,他的線程封閉完全靠實(shí)現(xiàn)者實(shí)現(xiàn)。也是最糟糕的一種線程封閉。所以我們直接把他忽略掉吧。
2:棧封閉
棧封閉是我們編程當(dāng)中遇到的最多的線程封閉。什么是棧封閉呢?簡單的說就是局部變量。多個(gè)線程訪問一個(gè)方法,此方法中的局部變量都會(huì)被拷貝一分兒到線程棧中。所以局部變量是不被多個(gè)線程所共享的,也就不會(huì)出現(xiàn)并發(fā)問題。所以能用局部變量就別用全局的變量,全局變量容易引起并發(fā)問題。
3:ThreadLocal封閉
使用ThreadLocal是實(shí)現(xiàn)線程封閉的最好方法,有興趣的朋友可以研究一下ThreadLocal的源碼,其實(shí)我們可以理解ThreadLocal內(nèi)部維護(hù)了一個(gè)Map,Map的key是每個(gè)線程的名稱,而Map的值就是我們要封閉的對(duì)象。每個(gè)線程中的對(duì)象都對(duì)應(yīng)著Map中一個(gè)值,也就是ThreadLocal利用Map實(shí)現(xiàn)了對(duì)象的線程封閉。
線程封閉詳解
線程封閉:當(dāng)訪問共享的可變數(shù)據(jù)時(shí),通常需要同步。一種避免同步的方式就是不共享數(shù)據(jù)。如果僅在單線程內(nèi)訪問數(shù)據(jù),就不需要同步,這種技術(shù)稱為線程封閉(thread confinement)
線程封閉技術(shù)一個(gè)常見的應(yīng)用就是JDBC的Connection對(duì)象,JDBC規(guī)范并沒有要求Connection對(duì)象必須是線程安全的,在服務(wù)器應(yīng)用程序中,線程從連接池獲取一個(gè)Connection對(duì)象,使用完之后將對(duì)象返還給連接池。下面介紹幾種線程封閉技術(shù):
1、Ad-hoc線程封閉
Ad-hoc線程封閉是指,維護(hù)線程的封閉性的職責(zé)完全由程序?qū)崿F(xiàn)承擔(dān),是非常脆弱的,因此在程序中盡量少使用,一般使用更強(qiáng)的線程封閉技術(shù),比如棧封閉或者ThreadLocal類。
2、棧封閉
棧封閉是線程封閉的一種特列,在棧封閉中,只能通過局部變量才能訪問對(duì)象。局部變量的固有屬性之一就是封閉在執(zhí)行棧中,其他線程無法訪問這個(gè)棧,棧封閉也稱為線程內(nèi)部使用或者線程局部使用。簡單的說就是局部變量。多個(gè)線程訪問一個(gè)方法,此方法中的局部變量都會(huì)被拷貝一分兒到線程棧中。所以局部變量是不被多個(gè)線程所共享的,也就不會(huì)出現(xiàn)并發(fā)問題。所以能用局部變量就別用全局的變量,全局變量容易引起并發(fā)問題。
比如下面的例子:
public int loadTheArk(Collection
在loadTheArk中實(shí)例化一個(gè)TreeSet對(duì)象,并將該對(duì)象的一個(gè)引用保存到animals中。此時(shí),只有一個(gè)引用指向集合animals,這個(gè)引用被封閉到局部變量中,因此也被封閉到局部變量中。然而,如果發(fā)布了對(duì)集合animals(或者該對(duì)象中的任何內(nèi)部數(shù)據(jù))的引用,那么封閉性將被破壞,并導(dǎo)致對(duì)象animals的逸出。
3、ThreadLocal類
維持線程封閉性的一種更加規(guī)范方法是使用ThreadLocal類,這個(gè)類能使線程中某個(gè)值與保存值的對(duì)象關(guān)聯(lián)起來。ThreadLocal類提供了get和set等訪問接口或者方法,這些方法為每個(gè)使用該變量的線程都存在一份獨(dú)立的副本,因此get總是放回當(dāng)前執(zhí)行線程在調(diào)用set設(shè)置的最新值??匆幌孪旅娲a例子:
public class ConnectionManager { private static ThreadLocal
通過調(diào)用ConnectionManager.getConnection()方法,每個(gè)線程獲取到的,都是自己獨(dú)立擁有的一個(gè)的Connection對(duì)象副本,第一次獲取時(shí),是通過initialValue()方法的返回值來設(shè)置值的。通過ConnectionManager.setConnection(Connection conn)方法設(shè)置的Connection對(duì)象,也只會(huì)和當(dāng)前線程綁定。這樣就實(shí)現(xiàn)了Connection對(duì)象在多個(gè)線程中的完全隔離。在Spring容器中管理多線程環(huán)境下的Connection對(duì)象時(shí),采用的思路和以上代碼非常相似。
每個(gè)線程是怎么和Connection對(duì)象副本綁定的?這個(gè)對(duì)象副本保存在哪里。當(dāng)某個(gè)線程初次調(diào)用ThreadLocal類的get方法時(shí),就會(huì)調(diào)用initialValue來獲取初始值,從概念上看,我們可以將ThreadLocal
下面我們來分析一下ThreadLocal類的源碼。ThreadLocal類的方法很簡單,只有四個(gè),分別為set,get,remove, initialValue,從字面上我們也能理解這些方法的作用。
public T get():返回當(dāng)前線程所對(duì)應(yīng)的局部變量。
public void set(T arg0):設(shè)置當(dāng)前線程局部變量的值。
public void remove():將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。注意,當(dāng)線程結(jié)束后,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。
protected T initialValue(): 對(duì)當(dāng)線程局部變量進(jìn)行初始化,并返回該初始值。是protected 屬性,顯然是讓子類進(jìn)行對(duì)其覆蓋重寫的,只有第一次調(diào)用set和get方法時(shí)才調(diào)用。
下面我們對(duì)這四個(gè)方法的源碼進(jìn)行分析,看看ThreadLocal類是如何實(shí)現(xiàn)這種“為每個(gè)線程提供不同的變量拷貝”。
3.1 set方法
以下是set方法的源碼
public void set(T arg0) { Thread arg1 = Thread.currentThread(); ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1); if (arg2 != null) { arg2.set(this, arg0); } else { this.createMap(arg1, arg0); } }
從set方法中可以看到,首先獲取當(dāng)前線程:Thread arg1 = Thread.currentThread();
再獲取當(dāng)前線程的ThreadLocalMap:ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);
判斷ThreadLocalMap是否為空,不為空,則以鍵值對(duì)的形式設(shè)置值,key為this,value就是局部變量的副本,this是當(dāng)前線程持有的ThreadLocal類實(shí)例化對(duì)象。
假如為空,則通過createMap方法創(chuàng)建。
我們看下getMap和createMap方法的源碼:
ThreadLocal.ThreadLocalMap getMap(Thread arg0) { return arg0.threadLocals;}void createMap(Thread arg0, T arg1) { arg0.threadLocals = new ThreadLocal.ThreadLocalMap(this, arg1); }
從代碼上已經(jīng)寫的非常清楚,每個(gè)線程都有自己的局部變量的副本,該副本是存在ThreadLocalMap 中,其中鍵值就是ThreadLocal類實(shí)例化對(duì)象。也就是說每個(gè)線程都擁有自己的ThreadLocalMap,ThreadLocalMap保存的就是局部變量副本。我們看一下java.lang.Thread源碼。
private static int threadInitNumber;ThreadLocalMap threadLocals = null;ThreadLocalMap inheritableThreadLocals = null;
3.2 get方法
public T get() { Thread arg0 = Thread.currentThread(); ThreadLocal.ThreadLocalMap arg1 = this.getMap(arg0); if (arg1 != null) { ThreadLocal.ThreadLocalMap.Entry arg2 = arg1.getEntry(this); if (arg2 != null) { Object arg3 = arg2.value; return arg3; } } return this.setInitialValue();}
從代碼上看,前兩步和set方法是一個(gè)樣的,分別獲取當(dāng)前線程和當(dāng)前線程的ThreadLocalMap,第三步判斷ThreadLocalMap是否為空,不為空根據(jù)this鍵值獲取value,為空調(diào)用setInitialValue()方法。
以下是setInitialValue方法代碼:
private T setInitialValue() { Object arg0 = this.initialValue(); Thread arg1 = Thread.currentThread(); ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1); if (arg2 != null) { arg2.set(this, arg0); } else { this.createMap(arg1, arg0); } return arg0;}
在setInitialValue里調(diào)用了initialValue()方法,也就是子類要重寫覆蓋的方法,對(duì)應(yīng)上面的例子的代碼是:
protected Connection initialValue() { Connection conn = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password"); } catch (SQLException e) { e.printStackTrace(); } return conn; }
然后獲取當(dāng)前線程和當(dāng)前線程的ThreadLocalMap,ThreadLocalMap為空則調(diào)用createMap,否則調(diào)用set方法。
到此,相信大家對(duì)“java線程封閉的方法是什么”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!