生活随笔
收集整理的這篇文章主要介紹了
彻底理解ThreadLocal
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
ynchronized這類線程同步的機制可以解決多線程并發問題,在這種解決方案下,多個線程訪問到的,都是同一份變量的內容。為了防止在多線程訪問的過程中,可能會出現的并發錯誤。不得不對多個線程的訪問進行同步,這樣也就意味著,多個線程必須先后對變量的值進行訪問或者修改,這是一種以延長訪問時間來換取線程安全性的策略。 ?
而ThreadLocal類為每一個線程都維護了自己獨有的變量拷貝。每個線程都擁有了自己獨立的一個變量,競爭條件被徹底消除了,那就沒有任何必要對這些線程進行同步,它們也能最大限度的由CPU調度,并發執行。并且由于每個線程在訪問該變量時,讀取和修改的,都是自己獨有的那一份變量拷貝,變量被徹底封閉在每個訪問的線程中,并發錯誤出現的可能也完全消除了。對比前一種方案,這是一種以空間來換取線程安全性的策略。
來看一個運用ThreadLocal來實現數據庫連接Connection對象線程隔離的例子。
| 01 | importjava.sql.Connection; |
| 02 | importjava.sql.DriverManager; |
| 03 | importjava.sql.SQLException; |
| 05 | publicclassConnectionManager { |
| 07 | privatestaticThreadLocal<Connection> connectionHolder =?newThreadLocal<Connection>() { |
| 09 | protectedConnection initialValue() { |
| 10 | Connection conn =?null; |
| 12 | conn = DriverManager.getConnection( |
| 13 | "jdbc:mysql://localhost:3306/test",?"username", |
| 15 | }?catch(SQLException e) { |
| 22 | publicstaticConnection getConnection() { |
| 23 | returnconnectionHolder.get(); |
| 26 | publicstaticvoidsetConnection(Connection conn) { |
| 27 | connectionHolder.set(conn); |
通過調用ConnectionManager.getConnection()方法,每個線程獲取到的,都是和當前線程綁定的那個Connection對象,第一次獲取時,是通過initialValue()方法的返回值來設置值的。通過ConnectionManager.setConnection(Connection conn)方法設置的Connection對象,也只會和當前線程綁定。這樣就實現了Connection對象在多個線程中的完全隔離。在Spring容器中管理多線程環境下的Connection對象時,采用的思路和以上代碼非常相似。
知其所以然
那么到底ThreadLocal類是如何實現這種“為每個線程提供不同的變量拷貝”的呢?先來看一下ThreadLocal的set()方法的源碼是如何實現的:
| 02 | * Sets the current thread's copy of this thread-local variable |
| 03 | * to the specified value. ?Most subclasses will have no need to |
| 04 | * override this method, relying solely on the {@link #initialValue} |
| 05 | * method to set the values of thread-locals. |
| 07 | * @param value the value to be stored in the current thread's copy of |
| 08 | * ? ? ? ?this thread-local. |
| 10 | publicvoidset(T value) { |
| 11 | Thread t = Thread.currentThread(); |
| 12 | ThreadLocalMap map = getMap(t); |
沒有什么魔法,在這個方法內部我們看到,首先通過getMap(Thread t)方法獲取一個和當前線程相關的ThreadLocalMap,然后將變量的值設置到這個ThreadLocalMap對象中,當然如果獲取到的ThreadLocalMap對象為空,就通過createMap方法創建。
線程隔離的秘密,就在于ThreadLocalMap這個類。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取(對比Map對象來理解),每個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有并發錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設置的對象了。
為了加深理解,我們接著看上面代碼中出現的getMap和createMap方法的實現:
| 1 | ThreadLocalMap getMap(Thread t) { |
| 1 | voidcreateMap(Thread t, T firstValue) { |
| 2 | t.threadLocals =?newThreadLocalMap(this, firstValue); |
代碼已經說的非常直白,就是獲取和設置Thread內的一個叫threadLocals的變量,而這個變量的類型就是ThreadLocalMap,這樣進一步驗證了上文中的觀點:每個線程都有自己獨立的ThreadLocalMap對象。打開java.lang.Thread類的源代碼,我們能得到更直觀的證明:
| 1 | /* ThreadLocal values pertaining to this thread. This map is maintained |
| 2 | * by the ThreadLocal class. */ |
| 3 | ThreadLocal.ThreadLocalMap threadLocals =?null; |
那么接下來再看一下ThreadLocal類中的get()方法,代碼是這么說的:
view sourceprint?
| 02 | * Returns the value in the current thread's copy of this |
| 03 | * thread-local variable. ?If the variable has no value for the |
| 04 | * current thread, it is first initialized to the value returned |
| 05 | * by an invocation of the {@link #initialValue} method. |
| 07 | * @return the current thread's value of this thread-local |
| 10 | Thread t = Thread.currentThread(); |
| 11 | ThreadLocalMap map = getMap(t); |
| 13 | ThreadLocalMap.Entry e = map.getEntry(this); |
| 17 | returnsetInitialValue(); |
| 21 | * Variant of set() to establish initialValue. Used instead |
| 22 | * of set() in case user has overridden the set() method. |
| 24 | * @return the initial value |
| 26 | privateT setInitialValue() { |
| 27 | T value = initialValue(); |
| 28 | Thread t = Thread.currentThread(); |
| 29 | ThreadLocalMap map = getMap(t); |
這兩個方法的代碼告訴我們,在獲取和當前線程綁定的值時,ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵進行查找的,這當然和前面set()方法的代碼是相呼應的。
進一步地,我們可以創建不同的ThreadLocal實例來實現多個變量在不同線程間的訪問隔離,為什么可以這么做?因為不同的ThreadLocal對象作為不同鍵,當然也可以在線程的ThreadLocalMap對象中設置不同的值了。通過ThreadLocal對象,在多線程中共享一個值和多個值的區別,就像你在一個HashMap對象中存儲一個鍵值對和多個鍵值對一樣,僅此而已。
設置到這些線程中的隔離變量,會不會導致內存泄漏呢?ThreadLocalMap對象保存在Thread對象中,當某個線程終止后,存儲在其中的線程隔離的變量,也將作為Thread實例的垃圾被回收掉,所以完全不用擔心內存泄漏的問題。在多個線程中隔離的變量,光榮的生,合理的死,真是圓滿,不是么?
最后再提一句,ThreadLocal變量的這種隔離策略,也不是任何情況下都能使用的。如果多個線程并發訪問的對象實例只允許,也只能創建那么一個,那就沒有別的辦法了,老老實實的使用同步機制來訪問吧。
我們想想線程安全是是什么?
多線程并發訪問共享資源,對共享資源造成的讀寫不一致,是為了解決這個問題才出現的線程安全的問題,說到底,是都要對共享資源讀寫。
現在這個threadlocal為每個線程搞了一個副本(相當于在每個線程內部加了一個變量,當然threadlocal與局部變量還是不一樣的,這里就不討論這個問題了,暫且先允許我這么說吧),讀寫操作與其他線程無關,這跟多線程都要讀取同一資源毛關系都沒有
本文轉自yunlielai51CTO博客,原文鏈接:http://blog.51cto.com/4925054/1282918,如需轉載請自行聯系原作者
總結
以上是生活随笔為你收集整理的彻底理解ThreadLocal的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。