java 中ThreadLocal本地線程和同步機(jī)制的比較
ThreadLocal的設(shè)計(jì)
首先看看ThreadLocal的接口:
Object get() ; // 返回當(dāng)前線程的線程局部變量副本 protected Object initialValue(); // 返回該線程局部變量的當(dāng)前線程的初始值 void set(Object value); // 設(shè)置當(dāng)前線程的線程局部變量副本的值
ThreadLocal有3個(gè)方法,其中值得注意的是initialValue(),該方法是一個(gè)protected的方法,顯然是為了子類重寫(xiě)而特意實(shí)現(xiàn)的。該方法返回當(dāng)前線程在該線程局部變量的初始值,這個(gè)方法是一個(gè)延遲調(diào)用方法,在一個(gè)線程第1次調(diào)用get()或者set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的確實(shí)實(shí)現(xiàn)直接返回一個(gè)null:
protected Object initialValue() { return null; }
ThreadLocal是如何做到為每一個(gè)線程維護(hù)變量的副本的呢?其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單,在ThreadLocal類中有一個(gè)Map,用于存儲(chǔ)每一個(gè)線程的變量的副本。
比如下面的示例實(shí)現(xiàn):就相當(dāng)于存Map 這個(gè)是ThreadLocal的get方法的實(shí)現(xiàn)
public T get() { Thread t = Thread.currentThread();//獲取當(dāng)前線程 ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocal與其它同步機(jī)制的比較
ThreadLocal和其它同步機(jī)制相比有什么優(yōu)勢(shì)呢?ThreadLocal和其它所有的同步機(jī)制都是為了解決多線程中的對(duì)同一變量的訪問(wèn)沖突,在普通的同步機(jī)制中,是通過(guò)對(duì)象加鎖來(lái)實(shí)現(xiàn)多個(gè)線程對(duì)同一變量的安全訪問(wèn)的。這時(shí)該變量是多個(gè)線程共享的,使用這種同步機(jī)制需要很細(xì)致地分析在什么時(shí)候?qū)ψ兞窟M(jìn)行讀寫(xiě),什么時(shí)候需要鎖定某個(gè)對(duì)象,什么時(shí)候釋放該對(duì)象的鎖等等很多。所有這些都是因?yàn)槎鄠€(gè)線程共享了資源造成的。ThreadLocal就從另一個(gè)角度來(lái)解決多線程的并發(fā)訪問(wèn),ThreadLocal會(huì)為每一個(gè)線程維護(hù)一個(gè)和該線程綁定的變量的副本,從而隔離了多個(gè)線程的數(shù)據(jù),每一個(gè)線程都擁有自己的變量副本,從而也就沒(méi)有必要對(duì)該變量進(jìn)行同步了。ThreadLocal提供了線程安全的共享對(duì)象,在編寫(xiě)多線程代碼時(shí),可以把不安全的整個(gè)變量封裝進(jìn)ThreadLocal,或者把該對(duì)象的特定于線程的狀態(tài)封裝進(jìn)ThreadLocal。
由于ThreadLocal中可以持有任何類型的對(duì)象,所以使用ThreadLocal get當(dāng)前線程的值是需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換。但隨著新的Java版本(1.5)將模版的引入,新的支持模版參數(shù)的ThreadLocal<T>類將從中受益。也可以減少?gòu)?qiáng)制類型轉(zhuǎn)換,并將一些錯(cuò)誤檢查提前到了編譯期,將一定程度地簡(jiǎn)化ThreadLocal的使用。
總結(jié)
當(dāng)然ThreadLocal并不能替代同步機(jī)制,兩者面向的問(wèn)題領(lǐng)域不同。同步機(jī)制是為了同步多個(gè)線程對(duì)相同資源的并發(fā)訪問(wèn),是為了多個(gè)線程之間進(jìn)行通信的有效方式;而ThreadLocal是隔離多個(gè)線程的數(shù)據(jù)共享,從根本上就不在多個(gè)線程之間共享資源(變量),這樣當(dāng)然不需要對(duì)多個(gè)線程進(jìn)行同步了。所以,如果你需要進(jìn)行多個(gè)線程之間進(jìn)行通信, 則使用同步機(jī)制;如果需要隔離多個(gè)線程之間的共享沖突,可以使用ThreadLocal,這將極大地簡(jiǎn)化你的程序,使程序更加易讀、簡(jiǎn)潔。
ThreadLocal常見(jiàn)用途:
存放當(dāng)前session用戶
存放一些context變量,比如webwork的ActionContext
存放session,比如Spring hibernate orm的session
例子:用 ThreadLocal 實(shí)現(xiàn)每線程 Singleton
線程局部變量常被用來(lái)描繪有狀態(tài)“單子”(Singleton) 或線程安全的共享對(duì)象,或者是通過(guò)把不安全的整個(gè)變量封裝進(jìn) ThreadLocal,或者是通過(guò)把對(duì)象的特定于線程的狀態(tài)封裝進(jìn) ThreadLocal。例如,在與數(shù)據(jù)庫(kù)有緊密聯(lián)系的應(yīng)用程序中,程序的很多方法可能都需要訪問(wèn)數(shù)據(jù)庫(kù)。在系統(tǒng)的每個(gè)方法中都包含一個(gè) Connection 作為參數(shù)是不方便的 — 用“單子”來(lái)訪問(wèn)連接可能是一個(gè)雖然更粗糙,但卻方便得多的技術(shù)。然而,多個(gè)線程不能安全地共享一個(gè) JDBC Connection。如清單 3 所示,通過(guò)使用“單子”中的 ThreadLocal,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection 的一個(gè)引用。這樣,我們可以認(rèn)為 ThreadLocal 允許我們創(chuàng)建每線程單子。
package org.heinrich.app.connection; import java.sql.Connection; public class ConnectionUtils { private final static ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); public Connection getConnection(){ Connection connection = threadLocal.get(); if(connection ==null){ connection = new DBHelper().getConn(); threadLocal.set(connection); } return connection; } } package org.heinrich.app.connection; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; //數(shù)據(jù)庫(kù)連接 public class DBHelper { public static final String url = "jdbc:mysql://localhost:3306/fk_test"; public static final String name = "com.mysql.jdbc.Driver"; public static final String user = "root"; public static final String password = "root"; public Connection conn = null; public Connection getConn() { try { Class.forName(name);// 指定連接類型 conn = DriverManager.getConnection(url, user, password);// 獲取連接 } catch (Exception e) { e.printStackTrace(); } return conn; } }
簡(jiǎn)單的實(shí)現(xiàn)Mysql連接線程安全的一種方式
理論上來(lái)說(shuō),ThreadLocal是的確是相對(duì)于每個(gè)線程,每個(gè)線程會(huì)有自己的ThreadLocal。但是上面已經(jīng)講到,一般的應(yīng)用服務(wù)器都會(huì)維護(hù)一套線程池。因此,不同用戶訪問(wèn),可能會(huì)接受到同樣的線程。因此,在做基于TheadLocal時(shí),需要謹(jǐn)慎,避免出現(xiàn)ThreadLocal變量的緩存,導(dǎo)致其他線程訪問(wèn)到本線程變量。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Spring boot redis cache的key的使用方法
這篇文章主要介紹了Spring boot redis cache的key的使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05Spring配置中transactionAttributes的使用方法介紹
這篇文章主要介紹了Spring配置中transactionAttributes的使用方法介紹的相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-09-09深入理解Java線程池從設(shè)計(jì)思想到源碼解讀
這篇文章主要介紹了深入理解Java線程池從設(shè)計(jì)思想到源碼解讀,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03SpringCloud超詳細(xì)講解微服務(wù)網(wǎng)關(guān)Gateway
這篇文章主要介紹了SpringCloud Gateway微服務(wù)網(wǎng)關(guān),負(fù)載均衡,熔斷和限流,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07快速解決List集合add元素,添加多個(gè)對(duì)象出現(xiàn)重復(fù)的問(wèn)題
這篇文章主要介紹了快速解決List集合add元素,添加多個(gè)對(duì)象出現(xiàn)重復(fù)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08