日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
搞懂Java并發(fā)—ThreadLocal

大家好,歡迎來(lái)到Tlog4J課堂,我是Jensen,今天咱們來(lái)簡(jiǎn)單聊聊ThreadLocal。

成都創(chuàng)新互聯(lián)專注于企業(yè)成都營(yíng)銷網(wǎng)站建設(shè)、網(wǎng)站重做改版、太倉(cāng)網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、成都h5網(wǎng)站建設(shè)、商城網(wǎng)站制作、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為太倉(cāng)等各大城市提供網(wǎng)站開(kāi)發(fā)制作服務(wù)。

ThreadLocal是通過(guò)線程隔離的方式防止任務(wù)在共享資源上產(chǎn)生沖突, 線程本地存儲(chǔ)是一種自動(dòng)化機(jī)制,可以為使用相同變量的每個(gè)不同線程都創(chuàng)建不同的存儲(chǔ)。

ThreadLocal簡(jiǎn)介

線程安全(是指廣義上的共享資源訪問(wèn)安全性,因?yàn)榫€程隔離是通過(guò)副本保證本線程訪問(wèn)資源安全性,它不保證線程之間還存在共享關(guān)系的狹義上的安全性)的解決思路:

  • 互斥同步:synchronized 和 ReentrantLock。
  • 非阻塞同步:CAS, AtomicXxx。
  • 無(wú)同步方案:棧封閉,本地存儲(chǔ)(Thread Local),可重入代碼。

本地存儲(chǔ)(Thread Local),在官網(wǎng)的解釋是這樣的:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID)

該類提供了線程局部 (thread-local) 變量。這些變量不同于它們的普通對(duì)應(yīng)物,因?yàn)樵L問(wèn)某個(gè)變量(通過(guò)其 get 或 set 方法)的每個(gè)線程都有自己的局部變量,它獨(dú)立于變量的初始化副本。ThreadLocal 實(shí)例通常是類中的 private static 字段,它們希望將狀態(tài)與某一個(gè)線程(例如,用戶 ID 或事務(wù) ID)相關(guān)聯(lián)。

總結(jié)而言:ThreadLocal是一個(gè)將在多線程中為每一個(gè)線程創(chuàng)建單獨(dú)的變量副本的類;當(dāng)使用ThreadLocal來(lái)維護(hù)變量時(shí), ThreadLocal會(huì)為每個(gè)線程創(chuàng)建單獨(dú)的變量副本, 避免因多線程操作共享變量而導(dǎo)致的數(shù)據(jù)不一致的情況。

ThreadLocal理解

提到ThreadLocal被提到應(yīng)用最多的是session管理和數(shù)據(jù)庫(kù)鏈接管理,這里以數(shù)據(jù)訪問(wèn)為例幫助你理解ThreadLocal:

如下數(shù)據(jù)庫(kù)管理類在單線程使用是沒(méi)有任何問(wèn)題的

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();
}
}

很顯然,在多線程中使用會(huì)存在線程安全問(wèn)題:

第一,這里面的2個(gè)方法都沒(méi)有進(jìn)行同步,很可能在openConnection方法中會(huì)多次創(chuàng)建connect。

第二,由于connect是共享變量,那么必然在調(diào)用connect的地方需要使用到同步來(lái)保障線程安全,因?yàn)楹芸赡芤粋€(gè)線程在使用connect進(jìn)行數(shù)據(jù)庫(kù)操作,而另外一個(gè)線程調(diào)用closeConnection關(guān)閉鏈接。

為了解決上述線程安全的問(wèn)題,第一考慮:互斥同步

你可能會(huì)說(shuō),將這段代碼的兩個(gè)方法進(jìn)行同步處理,并且在調(diào)用connect的地方需要進(jìn)行同步處理,比如用Synchronized或者ReentrantLock互斥鎖。

這里再拋出一個(gè)問(wèn)題:這地方到底需不需要將connect變量進(jìn)行共享?事實(shí)上是不需要的。

假如每個(gè)線程中都有一個(gè)connect變量,各個(gè)線程之間對(duì)connect變量的訪問(wèn)實(shí)際上是沒(méi)有依賴關(guān)系的,即一個(gè)線程不需要關(guān)心其他線程是否對(duì)這個(gè)connect進(jìn)行了修改的,修改后的代碼可以這樣:

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進(jìn)行操作
connectionManager.closeConnection();
}
}

這樣處理確實(shí)也沒(méi)有任何問(wèn)題,由于每次都是在方法內(nèi)部創(chuàng)建的連接,那么線程之間自然不存在線程安全問(wèn)題。

但是這樣會(huì)有一個(gè)致命的影響:導(dǎo)致服務(wù)器壓力非常大,并且嚴(yán)重影響程序的執(zhí)行性能。

由于在方法中需要頻繁地開(kāi)啟和關(guān)閉數(shù)據(jù)庫(kù)連接,這樣不僅嚴(yán)重影響程序執(zhí)行效率,還可能導(dǎo)致服務(wù)器壓力巨大。

ThreadLocal登場(chǎng)

那么這種情況下使用ThreadLocal是再適合不過(guò)的了,因?yàn)門hreadLocal在每個(gè)線程中對(duì)該變量會(huì)創(chuàng)建一個(gè)副本,即每個(gè)線程內(nèi)部都會(huì)有一個(gè)該變量,且在線程內(nèi)部任何地方都可以使用,線程之間互不影響,這樣一來(lái)就不存在線程安全問(wèn)題,也不會(huì)嚴(yán)重影響程序執(zhí)行性能。

下面就是網(wǎng)上出現(xiàn)最多的例子:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionManager {
private static final ThreadLocal dbConnectionLocal = new ThreadLocal() {
@Override
protected Connection initialValue() {
try {
return DriverManager.getConnection("", "", "");
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
};
public Connection getConnection() {
return dbConnectionLocal.get();
}
}

再注意下ThreadLocal的修飾符

ThreaLocal的JDK文檔中說(shuō)明:如果我們希望通過(guò)某個(gè)類將狀態(tài)(例如用戶ID、事務(wù)ID)與線程關(guān)聯(lián)起來(lái),那么通常在這個(gè)類中定義private static類型的ThreadLocal實(shí)例。

但是要注意,雖然ThreadLocal能夠解決上面說(shuō)的問(wèn)題,但是由于在每個(gè)線程中都創(chuàng)建了副本,所以要考慮它對(duì)資源的消耗,比如內(nèi)存的占用會(huì)比不使用ThreadLocal要大。

ThreadLocal原理

如何實(shí)現(xiàn)線程隔離

主要是用到了Thread對(duì)象中的一個(gè)ThreadLocalMap類型的變量threadLocals, 負(fù)責(zé)存儲(chǔ)當(dāng)前線程的關(guān)于Connection的對(duì)象, dbConnectionLocal(以上述例子中為例) 這個(gè)變量為Key, 以新建的Connection對(duì)象為Value。

這樣的話, 線程第一次讀取的時(shí)候如果不存在就會(huì)調(diào)用ThreadLocal的initialValue方法創(chuàng)建一個(gè)Connection對(duì)象并且返回。

具體關(guān)于為線程分配變量副本的代碼如下:

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
  • 首先獲取當(dāng)前線程對(duì)象t,然后從線程t中獲取到ThreadLocalMap的成員屬性threadLocals。
  • 如果當(dāng)前線程的threadLocals已經(jīng)初始化(即不為null) 并且存在以當(dāng)前ThreadLocal對(duì)象為Key的值,則直接返回當(dāng)前線程要獲取的對(duì)象(本例中為Connection)。
  • 如果當(dāng)前線程的threadLocals已經(jīng)初始化(即不為null)但是不存在以當(dāng)前ThreadLocal對(duì)象為Key的的對(duì)象,那么重新創(chuàng)建一個(gè)Connection對(duì)象,并且添加到當(dāng)前線程的threadLocals Map中,并返回。
  • 如果當(dāng)前線程的threadLocals屬性還沒(méi)有被初始化, 則重新創(chuàng)建一個(gè)ThreadLocalMap對(duì)象, 并且創(chuàng)建一個(gè)Connection對(duì)象并添加到ThreadLocalMap對(duì)象中并返回。

如果存在則直接返回很好理解, 那么對(duì)于初始化的代碼又是怎樣的呢?

private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
  • 首先調(diào)用我們上面寫的重載過(guò)后的initialValue方法,產(chǎn)生一個(gè)Connection對(duì)象。
  • 繼續(xù)查看當(dāng)前線程的threadLocals是不是空的, 如果ThreadLocalMap已被初始化, 那么直接將產(chǎn)生的對(duì)象添加到ThreadLocalMap中, 如果沒(méi)有初始化, 則創(chuàng)建并添加對(duì)象到其中。

同時(shí), ThreadLocal還提供了直接操作Thread對(duì)象中的threadLocals的方法。

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

這樣也可以不實(shí)現(xiàn)initialValue, 將初始化工作放到DBConnectionFactory的getConnection方法中:

public Connection getConnection() {
Connection connection = dbConnectionLocal.get();
if (connection == null) {
try {
connection = DriverManager.getConnection("", "", "");
dbConnectionLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}

那么看過(guò)代碼之后就很清晰的知道了為什么ThreadLocal能夠?qū)崿F(xiàn)變量的多線程隔離了; 其實(shí)就是用了Map的數(shù)據(jù)結(jié)構(gòu)給當(dāng)前線程緩存了, 要使用的時(shí)候就從本線程的threadLocals對(duì)象中獲取就可以了, key就是當(dāng)前線程。

當(dāng)然了,在當(dāng)前線程下獲取當(dāng)前線程里面的Map里面的對(duì)象并操作肯定沒(méi)有線程并發(fā)問(wèn)題了, 當(dāng)然能做到變量的線程間隔離了。

現(xiàn)在知道了ThreadLocal到底是什么了, 又知道了如何使用ThreadLocal以及其基本實(shí)現(xiàn)原理了是不是就可以結(jié)束了呢? 其實(shí)還有一個(gè)問(wèn)題就是ThreadLocalMap是個(gè)什么對(duì)象, 為什么要用這個(gè)對(duì)象呢?

ThreadLocalMap對(duì)象是什么

本質(zhì)上來(lái)講,它就是一個(gè)Map, 但是這個(gè)ThreadLocalMap與我們平時(shí)見(jiàn)到的Map有點(diǎn)不一樣

  • 它沒(méi)有實(shí)現(xiàn)Map接口。
  • 它沒(méi)有public的方法, 最多有一個(gè)default的構(gòu)造方法, 因?yàn)檫@個(gè)ThreadLocalMap的方法僅僅在ThreadLocal類中調(diào)用, 屬于靜態(tài)內(nèi)部類。
  • ThreadLocalMap的Entry實(shí)現(xiàn)繼承了WeakReference>。
  • 該方法僅僅用了一個(gè)Entry數(shù)組來(lái)存儲(chǔ)Key, Value; Entry并不是鏈表形式, 而是每個(gè)bucket里面僅僅放一個(gè)Entry。

要了解ThreadLocalMap的實(shí)現(xiàn), 我們先從入口開(kāi)始, 就是往該Map中添加一個(gè)值:

private void set(ThreadLocal key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

先進(jìn)行簡(jiǎn)單的分析, 對(duì)該代碼表層意思進(jìn)行解讀:

  • 看下當(dāng)前threadLocal的在數(shù)組中的索引位置 比如: i = 2, 看 i = 2 位置上面的元素(Entry)的Key是否等于threadLocal 這個(gè) Key, 如果等于就很好說(shuō)了, 直接將該位置上面的Entry的Value替換成最新的就可以了。
  • 如果當(dāng)前位置上面的 Entry 的 Key為空, 說(shuō)明ThreadLocal對(duì)象已經(jīng)被回收了, 那么就調(diào)用replaceStaleEntry。
  • 如果清理完無(wú)用條目(ThreadLocal被回收的條目)、并且數(shù)組中的數(shù)據(jù)大小 > 閾值的時(shí)候?qū)Ξ?dāng)前的Table進(jìn)行重新哈希 所以, 該HashMap是處理沖突檢測(cè)的機(jī)制是向后移位, 清除過(guò)期條目 最終找到合適的位置。

了解完Set方法, 后面就是Get方法了:

private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}

先找到ThreadLocal的索引位置, 如果索引位置處的entry不為空并且鍵與threadLocal是同一個(gè)對(duì)象, 則直接返回; 否則去后面的索引位置繼續(xù)查找。

ThreadLocal造成內(nèi)存泄露的問(wèn)題

網(wǎng)上有這樣一個(gè)例子:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalDemo {
static class LocalVariable {
private Long[] a = new Long[1024 * 1024];
}
// (1)
final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,new LinkedBlockingQueue<>());
// (2)
final static ThreadLocal localVariable = new ThreadLocal();
public static void main(String[] args) throws InterruptedException {
// (3)
Thread.sleep(5000 * 4);
for (int i = 0; i < 50; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
// (4)
localVariable.set(new LocalVariable());
// (5)
System.out.println("use local varaible" + localVariable.get());
localVariable.remove();
}
});
}
// (6)
System.out.println("pool execute over");
}
}

如果用線程池來(lái)操作ThreadLocal 對(duì)象確實(shí)會(huì)造成內(nèi)存泄露, 因?yàn)閷?duì)于線程池里面不會(huì)銷毀的線程, 里面總會(huì)存在著的強(qiáng)引用。

這是因?yàn)閒inal static 修飾的 ThreadLocal 并不會(huì)釋放, 而ThreadLocalMap 對(duì)于 Key 雖然是弱引用, 但是強(qiáng)引用不會(huì)釋放, 弱引用當(dāng)然也會(huì)一直有值, 同時(shí)創(chuàng)建的LocalVariable對(duì)象也不會(huì)釋放, 就造成了內(nèi)存泄露。

如果LocalVariable對(duì)象不是一個(gè)大對(duì)象的話, 其實(shí)泄露的并不嚴(yán)重, 泄露的內(nèi)存 = 核心線程數(shù) * LocalVariable對(duì)象的大小。

所以, 為了避免出現(xiàn)內(nèi)存泄露的情況, ThreadLocal提供了一個(gè)清除線程中對(duì)象的方法, 即 remove, 其實(shí)內(nèi)部實(shí)現(xiàn)就是調(diào)用 ThreadLocalMap 的remove方法:

private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}

找到Key對(duì)應(yīng)的Entry, 并且清除Entry的Key(ThreadLocal)置空, 隨后清除過(guò)期的Entry即可避免內(nèi)存泄露。

ThreadLocal應(yīng)用場(chǎng)景

每個(gè)線程維護(hù)了一個(gè)“序列號(hào)”

再回想上文說(shuō)的,如果我們希望通過(guò)某個(gè)類將狀態(tài)(例如用戶ID、事務(wù)ID)與線程關(guān)聯(lián)起來(lái),那么通常在這個(gè)類中定義private static類型的ThreadLocal 實(shí)例。

public class SerialNum {
// The next serial number to be assigned
private static int nextSerialNum = 0;
private static ThreadLocal serialNum = new ThreadLocal() {
protected synchronized Object initialValue() {
return new Integer(nextSerialNum++);
}
};
public static int get() {
return ((Integer) (serialNum.get())).intValue();
}
}

Session的管理

經(jīng)典的另外一個(gè)例子:

private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}

在線程內(nèi)部創(chuàng)建ThreadLocal

還有一種用法是在線程類內(nèi)部創(chuàng)建ThreadLocal,基本步驟如下:

  • 在多線程的類(如ThreadDemo類)中,創(chuàng)建一個(gè)ThreadLocal對(duì)象threadXxx,用來(lái)保存線程間需要隔離處理的對(duì)象xxx。
  • 在ThreadDemo類中,創(chuàng)建一個(gè)獲取要隔離訪問(wèn)的數(shù)據(jù)的方法getXxx(),在方法中判斷,若ThreadLocal對(duì)象為null時(shí)候,應(yīng)該new()一個(gè)隔離訪問(wèn)類型的對(duì)象,并強(qiáng)制轉(zhuǎn)換為要應(yīng)用的類型。
  • 在ThreadDemo類的run()方法中,通過(guò)調(diào)用getXxx()方法獲取要操作的數(shù)據(jù),這樣可以保證每個(gè)線程對(duì)應(yīng)一個(gè)數(shù)據(jù)對(duì)象,在任何時(shí)刻都操作的是這個(gè)對(duì)象。
public class ThreadLocalTest implements Runnable{

ThreadLocal StudentThreadLocal = new ThreadLocal();
@Override
public void run() {
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running...");
Random random = new Random();
int age = random.nextInt(100);
System.out.println(currentThreadName + " is set age: " + age);
Student Student = getStudentt(); //通過(guò)這個(gè)方法,為每個(gè)線程都獨(dú)立的new一個(gè)Studentt對(duì)象,每個(gè)線程的的Studentt對(duì)象都可以設(shè)置不同的值
Student.setAge(age);
System.out.println(currentThreadName + " is first get age: " + Student.getAge());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( currentThreadName + " is second get age: " + Student.getAge());
}

private Student getStudentt() {
Student Student = StudentThreadLocal.get();
if (null == Student) {
Student = new Student();
StudentThreadLocal.set(Student);
}
return Student;
}
public static void main(String[] args) {
ThreadLocalTest t = new ThreadLocalTest();
Thread t1 = new Thread(t,"Thread A");
Thread t2 = new Thread(t,"Thread B");
t1.start();
t2.start();
}
}
class Student{
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

java 開(kāi)發(fā)手冊(cè)中推薦的 ThreadLocal

看看阿里巴巴 java 開(kāi)發(fā)手冊(cè)中推薦的 ThreadLocal 的用法:

import java.text.DateFormat;
import java.text.SimpleDateFormat;
public class DateUtils {
public static final ThreadLocal threadLocal = new ThreadLocal(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
}

然后我們?cè)僖玫?DateFormat 對(duì)象的地方,這樣調(diào)用:

DateUtils.df.get().format(new Date());

分享名稱:搞懂Java并發(fā)—ThreadLocal
網(wǎng)頁(yè)地址:http://www.dlmjj.cn/article/dhijgsc.html