日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

详解线程本地变量ThreadLocal

發布時間:2025/1/21 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解线程本地变量ThreadLocal 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

并發應用的一個關鍵地方就是共享數據。如果你創建一個類對象,實現Runnable接口,然后多個Thread對象使用同樣的Runnable對象,全部的線程都共享同樣的屬性。這意味著,如果你在一個線程里改變一個屬性,全部的線程都會受到這個改變的影響。
有時,你希望程序里的各個線程的屬性不會被共享。 Java 并發 API提供了一個很清楚的機制叫本地線程變量即ThreadLocal。
模擬ThreadLocal類實現:線程范圍內的共享變量,每個線程只能訪問他自己的,不能訪問別的線程。

一、本地線程變量使用場景

并發應用的一個關鍵地方就是共享數據。如果你創建一個類對象,實現Runnable接口,然后多個Thread對象使用同樣的Runnable對象,全部的線程都共享同樣的屬性。這意味著,如果你在一個線程里改變一個屬性,全部的線程都會受到這個改變的影響。
有時,你希望程序里的各個線程的屬性不會被共享。 Java 并發 API提供了一個很清楚的機制叫本地線程變量即ThreadLocal。
模擬ThreadLocal類實現:線程范圍內的共享變量,每個線程只能訪問他自己的,不能訪問別的線程。

二、對ThreadLocal的理解

ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做本地線程變量,其實意思差不多。
ThreadLocal和本地線程沒有半毛錢關系,更不是一個特殊的Thread,它只是一個線程的局部變量(其實就是一個Map用于存儲每一個線程的變量副本,Map中元素的Key為線程對象,而Value對應線程的變量副本),ThreadLocal會為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
對于多線程資源共享的問題,同步機制(Synchronized)采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
官方對ThreadLocal的描述:

1、每個線程都有自己的局部變量
每個線程都有一個獨立于其他線程的上下文來保存這個變量,一個線程的本地變量對其他線程是不可見的(有前提,后面解釋) 2、獨立于變量的初始化副本
ThreadLocal可以給一個初始值,而每個線程都會獲得這個初始化值的一個副本,這樣才能保證不同的線程都有一份拷貝。
3、狀態與某一個線程相關聯 ThreadLocal
不是用于解決共享變量的問題的,不是為了協調線程同步而存在,而是為了方便每個線程處理自己的狀態而引入的一個機制,理解這點對正確使用ThreadLocal至關重要。
通過ThreadLocal存取的數據,總是與當前線程相關,也就是說,JVM
為每個運行的線程,綁定了私有的本地實例存取空間,從而為多線程環境常出現的并發訪問問題提供了一種隔離機制。 我們還是先來看一個例子:

class ConnectionManager {private static Connection connect = null;public static Connection openConnection() {if(connect == null){connect = DriverManager.getConnection();}return connect;}public static void closeConnection() {if(connect!=null)connect.close();} }

假設有這樣一個數據庫鏈接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果在多線程中使用呢?很顯然,在多線程中使用會存在線程安全問題:第一,這里面的2個方法都沒有進行同步,很可能在openConnection方法中會多次創建connect;第二,由于connect是共享變量,那么必然在調用connect的地方需要使用到同步來保障線程安全,因為很可能一個線程在使用connect進行數據庫操作,而另外一個線程調用closeConnection關閉鏈接。

所以出于線程安全的考慮,必須將這段代碼的兩個方法進行同步處理,并且在調用connect的地方需要進行同步處理。

這樣將會大大影響程序執行效率,因為一個線程在使用connect進行數據庫操作的時候,其他線程只有等待。

那么大家來仔細分析一下這個問題,這地方到底需不需要將connect變量進行共享?事實上,是不需要的。假如每個線程中都有一個connect變量,各個線程之間對connect變量的訪問實際上是沒有依賴關系的,即一個線程不需要關心其他線程是否對這個connect進行了修改的。

到這里,可能會有朋友想到,既然不需要在線程之間共享這個變量,可以直接這樣處理,在每個需要使用數據庫連接的方法中具體使用時才創建數據庫鏈接,然后在方法調用完畢再釋放這個連接。比如下面這樣:

class ConnectionManager {private Connection connect = null;public Connection openConnection() {if(connect == null){connect = DriverManager.getConnection();}return connect;}public void closeConnection() {if(connect!=null)connect.close();} }class Dao{public void insert() {ConnectionManager connectionManager = new ConnectionManager();Connection connection = connectionManager.openConnection();//使用connection進行操作connectionManager.closeConnection();} }

這樣處理確實也沒有任何問題,由于每次都是在方法內部創建的連接,那么線程之間自然不存在線程安全問題。但是這樣會有一個致命的影響:導致服務器壓力非常大,并且嚴重影響程序執行性能。由于在方法中需要頻繁地開啟和關閉數據庫連接,這樣不盡嚴重影響程序執行效率,還可能導致服務器壓力巨大。

那么這種情況下使用ThreadLocal是再適合不過的了,因為ThreadLocal在每個線程中對該變量會創建一個副本,即每個線程內部都會有一個該變量,且在線程內部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會嚴重影響程序執行性能。

但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由于在每個線程中都創建了副本,所以要考慮它對資源的消耗,比如內存的占用會比不使用ThreadLocal要大。

三、深入解析ThreadLocal類

在上面談到了對ThreadLocal的一些理解,那我們下面來看一下具體ThreadLocal是如何實現的。
先了解一下ThreadLocal類提供的幾個方法:

public T get() { } public void set(T value) { } public void remove() { } protected T initialValue() { }

get()方法是用來獲取ThreadLocal在當前線程中保存的變量副本; set()用來設置當前線程中變量的副本;
remove()用來移除當前線程中變量的副本;
initialValue()是一個protected方法,一般是用來在使用時進行重寫的,它是一個延遲加載方法;

ThreadLocal是如何為每個線程創建變量的副本的:
1、在每個線程Thread內部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,Key為當前ThreadLocal變量,value為變量副本(即T類型的變量)。
2、初始時,在Thread里面,threadLocals為空,當通過ThreadLocal變量調用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,并且以當前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。
3、在當前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。
下面通過一個例子來證明通過ThreadLocal能達到在每個線程中創建變量副本的效果:

public class Test {ThreadLocal<Long> longLocal = new ThreadLocal<Long>();ThreadLocal<String> stringLocal = new ThreadLocal<String>();public void set() {longLocal.set(Thread.currentThread().getId());stringLocal.set(Thread.currentThread().getName());}public long getLong() {return longLocal.get();}public String getString() {return stringLocal.get();}public static void main(String[] args) throws InterruptedException {final Test test = new Test();test.set();System.out.println(test.getLong());System.out.println(test.getString());Thread thread1 = new Thread(){public void run() {test.set();System.out.println(test.getLong());System.out.println(test.getString());};};thread1.start();thread1.join();System.out.println(test.getLong());System.out.println(test.getString());} }

這段代碼的輸出結果為:

從這段代碼的輸出結果可以看出,在main線程中和thread1線程中,longLocal保存的副本值和stringLocal保存的副本值都不一樣。最后一次在main線程再次打印副本值是為了證明在main線程中和thread1線程中的副本值確實是不同的。

總結一下:

1)實際的通過ThreadLocal創建的副本是存儲在每個線程自己的threadLocals中的;

2)為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象,因為每個線程中可有多個threadLocal變量,就像上面代碼中的longLocal和stringLocal;

四.ThreadLocal的應用場景

最常見的ThreadLocal使用場景為:用來解決數據庫連接、Session管理,多線程單例模式訪問;

訂單處理包含一系列操作:減少庫存量、增加一條流水臺賬、修改總賬,這幾個操作要在同一個事務中完成,通常也即同一個線程中進行處理,如果累加公司應收款的操作失敗了,則應該把前面的操作回滾,否則,提交所有操作,這要求這些操作使用相同的數據庫連接對象,而這些操作的代碼分別位于不同的模塊類中。
銀行轉賬包含一系列操作:
把轉出帳戶的余額減少,把轉入帳戶的余額增加,這兩個操作要在同一個事務中完成,它們必須使用相同的數據庫連接對象,轉入和轉出操作的代碼分別是兩個不同的帳戶對象的方法。

我們先看一個簡單的例子:

public class ThreadLocalTest {//創建一個Integer型的線程本地變量public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return 0;}};public static void main(String[] args) throws InterruptedException {Thread[] threads = new Thread[5];for (int j = 0; j < 5; j++) { threads[j] = new Thread(new Runnable() {@Overridepublic void run() {//獲取當前線程的本地變量,然后累加5次int num = local.get();for (int i = 0; i < 5; i++) {num++;}//重新設置累加后的本地變量local.set(num);System.out.println(Thread.currentThread().getName() + " : "+ local.get());}}, "Thread-" + j);}for (Thread thread : threads) {thread.start();}} }

運行后結果:

Thread-0 : 5Thread-4 : 5Thread-2 : 5Thread-1 : 5Thread-3 : 5

我們看到,每個線程累加后的結果都是5,各個線程處理自己的本地變量值,線程之間互不影響。

如:數據庫連接:

Session連接:

五、ThreadLocal使用的一般步驟

1、在多線程的類(如ThreadDemo類)中,創建一個ThreadLocal對象threadXxx,用來保存線程間需要隔離處理的對象xxx。
2、在ThreadDemo類中,創建一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象為null時候,應該new()一個隔離訪問類型的對象,并強制轉換為要應用的類型。
3、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數據,這樣可以保證每個線程對應一個數據對象,在任何時刻都操作的是這個對象。
相關鏈接請參考:http://my.oschina.net/xianggao/blog/392440?fromerr=nKHw4fBT

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的详解线程本地变量ThreadLocal的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: xxxx日本少妇 | 中文字幕乱码人妻无码久久95 | 丰满人妻一区二区三区精品高清 | 熟妇人妻中文av无码 | 91久热| 亚洲国产日韩av | 色久月 | 国产精品一区二区电影 | 国产亚洲女人久久久久毛片 | 加勒比成人在线 | 国产毛片一区二区三区va在线 | 天堂在线视频网站 | 国产少女免费观看高清 | 性欧美一区二区三区 | 亚洲日本中文字幕在线 | 密桃成熟时在线观看 | 四虎国产精品永久免费观看视频 | 国产精品日韩一区二区 | 成人女同av免费观看 | www.在线观看麻豆 | 看看黄色片 | 亚洲无套 | 国产亚洲美女精品久久久2020 | 黄色a网| 成年女人18级毛片毛片免费 | 我爱我色成人网 | 国产美女福利视频 | 亚洲一区二区乱码 | 欧美三级理论片 | 老女人一区 | 天堂精品一区 | 台湾佬中文在线 | 日本黄色aaa | 日本人妖网站 | 婷婷丁香激情五月 | 免费看日产一区二区三区 | 奇米影视在线观看 | 福利av在线 | 日韩精品国产一区 | 中文字幕第22页 | y11111少妇| 1区2区视频 | 99re视频 | 午夜免费小视频 | 男人的天堂aa | 午夜福利毛片 | 中文字幕+乱码+中文乱码www | 午夜精品福利电影 | 中文字幕日韩一区二区三区 | av一区二区不卡 | 久久国语精品 | 高清一区二区三区四区五区 | 日本不卡一区二区三区 | 椎名由奈在线观看 | 91av在线看 | 午夜视频一区二区 | 黄金网站在线观看 | 五月色婷| 成人午夜黄色 | 在线看免费毛片 | 麻豆视屏 | 国产亚洲精品女人久久久久久 | 污污视频免费观看 | 四虎影视永久免费 | 国产在线天堂 | 尤物视频在线观看视频 | 致命弯道8在线观看免费高清完整 | 四虎黄色影视 | 中文在线视频观看 | 久久精品免费 | 免费看黄网站在线观看 | 国产美女免费无遮挡 | 自拍偷拍国产 | 国产黄大片在线观看画质优化 | 最新在线视频 | 免费欧美黄色片 | av一区二区三区免费观看 | 午夜一区二区三区四区 | 午夜成人鲁丝片午夜精品 | 欧美操操操 | 伊人久久综合影院 | 性高潮视频在线观看 | 狠狠五月天 | 三级黄色免费 | 天堂在线视频 | 91蜜桃视频在线观看 | 久久免费视频观看 | 亚洲春色一区二区三区 | 一区二区三区www污污污网站 | www色网站| 91原视频| 中国挤奶哺乳午夜片 | 亚洲国产免费看 | 手机在线中文字幕 | 国产精品久久综合视频 | h片免费观看 | 成人无码av片在线观看 | 玩偶游戏在线观看免费 | 脱裤吧导航 |