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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java并发编程--理解ThreadLocal

發(fā)布時(shí)間:2023/11/30 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java并发编程--理解ThreadLocal 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

另一篇博文:Hibernet中的ThreadLocal使用 http://www.cnblogs.com/gnivor/p/4440776.html

本文參考:
http://blog.csdn.net/lufeng20/article/details/24314381
http://www.cnblogs.com/chenying99/articles/3405161.html

??

ThreadLocal類接口很簡(jiǎn)單,只有4個(gè)方法,我們先來(lái)了解一下:?

void set(Object value)設(shè)置當(dāng)前線程的線程局部變量的值。?
public Object get()該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量。?
public void remove()將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。
注意,當(dāng)線程結(jié)束后,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。?
protected Object initialValue()返回該線程局部變量的初始值,該方法是一個(gè)protected的方法,顯然是為了讓子類覆蓋而設(shè)計(jì)的。
這個(gè)方法是一個(gè)延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實(shí)現(xiàn)直接返回一個(gè)null。

?

一、知其然

synchronized這類線程同步的機(jī)制可以解決多線程并發(fā)問(wèn)題,在這種解決方案下,多個(gè)線程訪問(wèn)到的,都是同一份變量的內(nèi)容。為了防止在多線程訪問(wèn)的過(guò)程中,可能會(huì)出現(xiàn)的并發(fā)錯(cuò)誤。不得不對(duì)多個(gè)線程的訪問(wèn)進(jìn)行同步,這樣也就意味著,多個(gè)線程必須先后對(duì)變量的值進(jìn)行訪問(wèn)或者修改,這是一種以延長(zhǎng)訪問(wèn)時(shí)間來(lái)?yè)Q取線程安全性的策略。

而ThreadLocal類為每一個(gè)線程都維護(hù)了自己獨(dú)有的變量拷貝。每個(gè)線程都擁有了自己獨(dú)立的一個(gè)變量,競(jìng)爭(zhēng)條件被徹底消除了,那就沒(méi)有任何必要對(duì)這些線程進(jìn)行同步,它們也能最大限度的由CPU調(diào)度,并發(fā)執(zhí)行。并且由于每個(gè)線程在訪問(wèn)該變量時(shí),讀取和修改的,都是自己獨(dú)有的那一份變量拷貝,變量被徹底封閉在每個(gè)訪問(wèn)的線程中,并發(fā)錯(cuò)誤出現(xiàn)的可能也完全消除了。對(duì)比前一種方案,這是一種以空間來(lái)?yè)Q取線程安全性的策略。

來(lái)看一個(gè)運(yùn)用ThreadLocal來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接Connection對(duì)象線程隔離的例子。

import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException;public class ConnectionManager { private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { @Override protected Connection initialValue() { Connection conn = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password"); } catch (SQLException e) { e.printStackTrace(); } return conn; } }; public static Connection getConnection() { return connectionHolder.get(); } public static void setConnection(Connection conn) { connectionHolder.set(conn); } }

  通過(guò)調(diào)用ConnectionManager.getConnection()方法,每個(gè)線程獲取到的,都是和當(dāng)前線程綁定的那個(gè)Connection對(duì)象,第一次獲取時(shí),是通過(guò)initialValue()方法的返回值來(lái)設(shè)置值的。通過(guò)ConnectionManager.setConnection(Connection conn)方法設(shè)置的Connection對(duì)象,也只會(huì)和當(dāng)前線程綁定。這樣就實(shí)現(xiàn)了Connection對(duì)象在多個(gè)線程中的完全隔離。在Spring容器中管理多線程環(huán)境下的Connection對(duì)象時(shí),采用的思路和以上代碼非常相似。

附:另一個(gè)例子?

public class TestNum { private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){public Integer initialValue() { return 0;}};public int getNextNum(){seqNum.set(seqNum.get()+1);return seqNum.get();}public static void main(String[] args) { TestNum sn = new TestNum(); //三個(gè)線程共享SN 產(chǎn)生序列號(hào)ThreadClient t1 = new ThreadClient(sn);ThreadClient t2 = new ThreadClient(sn);ThreadClient t3 = new ThreadClient(sn);t1.start();t2.start();t3.start();} }class ThreadClient extends Thread{private TestNum sn ;public ThreadClient(TestNum sn){this.sn = sn;}public void run(){for(int i = 0 ; i < 3 ; i++){System.out.println("Thread: "+ Thread.currentThread().getName() + " sn: " + sn.getNextNum());}} } View Code

??

二、知其所以然

那么到底ThreadLocal類是如何實(shí)現(xiàn)這種“為每個(gè)線程提供不同的變量拷貝”的呢?先來(lái)看一下ThreadLocal的set()方法的源碼是如何實(shí)現(xiàn)的:?

/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 在這個(gè)方法內(nèi)部我們看到,首先通過(guò)getMap(Thread t)方法獲取一個(gè)和當(dāng)前線程相關(guān)的ThreadLocalMap,然后將變量的值設(shè)置到這個(gè)ThreadLocalMap對(duì)象中,當(dāng)然如果獲取到的ThreadLocalMap對(duì)象為空,就通過(guò)createMap方法創(chuàng)建。

  線程隔離的秘密,就在于ThreadLocalMap這個(gè)類。ThreadLocalMap是ThreadLocal類的一個(gè)靜態(tài)內(nèi)部類,它實(shí)現(xiàn)了鍵值對(duì)的設(shè)置和獲取(對(duì)比Map對(duì)象來(lái)理解),每個(gè)線程中都有一個(gè)獨(dú)立的ThreadLocalMap副本,它所存儲(chǔ)的值,只能被當(dāng)前線程讀取和修改。ThreadLocal類通過(guò)操作每一個(gè)線程特有的ThreadLocalMap副本,從而實(shí)現(xiàn)了變量訪問(wèn)在不同線程中的隔離。因?yàn)槊總€(gè)線程的變量都是自己特有的,完全不會(huì)有并發(fā)錯(cuò)誤。還有一點(diǎn)就是,ThreadLocalMap存儲(chǔ)的鍵值對(duì)中的鍵是this對(duì)象指向的ThreadLocal對(duì)象,而值就是你所設(shè)置的對(duì)象了。

為了加深理解,我們接著看上面代碼中出現(xiàn)的getMapcreateMap方法的實(shí)現(xiàn):?

/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

?代碼已經(jīng)說(shuō)的非常直白,就是獲取和設(shè)置Thread內(nèi)的一個(gè)叫threadLocals的變量,而這個(gè)變量的類型就是ThreadLocalMap,這樣進(jìn)一步驗(yàn)證了上文中的觀點(diǎn):每個(gè)線程都有自己獨(dú)立的ThreadLocalMap對(duì)象。打開java.lang.Thread類的源代碼,我們能得到更直觀的證明:

/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;

?

那么接下來(lái)再看一下ThreadLocal類中的get()方法,代碼是這么說(shuō)的:?

/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }

??

再來(lái)看setInitialValue()方法:?

/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; } ?

?

這兩個(gè)方法的代碼告訴我們,在獲取和當(dāng)前線程綁定的值時(shí),ThreadLocalMap對(duì)象是以this指向的ThreadLocal對(duì)象為鍵進(jìn)行查找的,這當(dāng)然和前面set()方法的代碼是相呼應(yīng)的。

進(jìn)一步地,我們可以創(chuàng)建不同的ThreadLocal實(shí)例來(lái)實(shí)現(xiàn)多個(gè)變量在不同線程間的訪問(wèn)隔離,為什么可以這么做?因?yàn)椴煌腡hreadLocal對(duì)象作為不同鍵,當(dāng)然也可以在線程的ThreadLocalMap對(duì)象中設(shè)置不同的值了。通過(guò)ThreadLocal對(duì)象,在多線程中共享一個(gè)值和多個(gè)值的區(qū)別,就像你在一個(gè)HashMap對(duì)象中存儲(chǔ)一個(gè)鍵值對(duì)和多個(gè)鍵值對(duì)一樣,僅此而已。

設(shè)置到這些線程中的隔離變量,會(huì)不會(huì)導(dǎo)致內(nèi)存泄漏呢?ThreadLocalMap對(duì)象保存在Thread對(duì)象中,當(dāng)某個(gè)線程終止后,存儲(chǔ)在其中的線程隔離的變量,也將作為Thread實(shí)例的垃圾被回收掉,所以完全不用擔(dān)心內(nèi)存泄漏的問(wèn)題。在多個(gè)線程中隔離的變量,光榮的生,合理的死,真是圓滿,不是么?

最后再提一句,ThreadLocal變量的這種隔離策略,也不是任何情況下都能使用的。如果多個(gè)線程并發(fā)訪問(wèn)的對(duì)象實(shí)例只允許,也只能創(chuàng)建一個(gè),那就沒(méi)有別的辦法了,老老實(shí)實(shí)的使用同步機(jī)制(synchronized)來(lái)訪問(wèn)吧。

http://my.oschina.net/lichhao/blog/111362

轉(zhuǎn)載于:https://www.cnblogs.com/gnivor/p/4904793.html

總結(jié)

以上是生活随笔為你收集整理的Java并发编程--理解ThreadLocal的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。