Java線程變量ThreadLocal詳細解讀
Java線程變量ThreadLocal
多線程訪問同一個變量的時候,很容易出現(xiàn)問題,特別是多線程對一個共享變量進行寫入的時候。為了線程的安全在進行數(shù)據(jù)寫入時候會進行數(shù)據(jù)的同步。
如上圖: 如果要想實現(xiàn)多個線程操作同一個數(shù)據(jù)而不被其他線程所影響,就必須加鎖操作,加鎖又增加了使用的復(fù)雜度。
ThredLocal的用處就是當多個線程操作同一個變量的時候可以在創(chuàng)建一個ThredLocal,把共享變量復(fù)制到線程的本地threadLocals(threadLocals是Thread的成員變量) 中,這樣各個線程進行操作的時候只能操作自己本地數(shù)據(jù),這樣就不會存儲在線程不安全的問題了。
下面舉個例子:
public class ThreadLocalTest { // 創(chuàng)建一個ThreadLocal static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); static Integer num = 5; public static void main(String[] args) throws InterruptedException { new Thread(()->{ // 線程1 把num 同步到threadLocal中 threadLocal.set(num); // 線程1 獲取threadLocal中的數(shù)據(jù) System.out.println("線程1: " + threadLocal.get()); }).start(); new Thread(()->{ // 線程2 獲取threadLocal中的數(shù)據(jù) System.out.println("線程2: "+ threadLocal.get()); }).start(); System.out.println("主線程: " + threadLocal.get()); } }
結(jié)果:由上面的返回可以知道,當在線程1設(shè)置了設(shè)置了數(shù)據(jù)時在線程2是訪問不到的**,主線程**也訪問不到。
ThreadLocal的實現(xiàn)原理
由下圖可知 Thread類中有一個成員變量 ThreadLocal.ThreadLocalMap **threadLocals **= **null ** 是ThreadLocalMap類型的,本地變量都是存儲在ThreadLocalMap中的,ThreadLocal只是一個外殼。ThreadLocalMap是ThreadLocal中的內(nèi)部類,是一個定制化的Map形式,那就代表一個線程可以有多個ThreadLocal本地變量。key就是ThreadLocal本身,value就是存儲的本地變量。
1.set()
public void set(T value) { // 獲取當前線程 Thread t = Thread.currentThread(); // 獲取線程的成員變量threadLocals ThreadLocalMap map = getMap(t); if (map != null) // ThreadLoacl 對象為key 同步的變量為value map.set(this, value); else // 如果是第一次 則創(chuàng)建ThreadLocalMap 并且賦值 createMap(t, value); } // 獲取線程的成員變量threadLocals ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // 第一次創(chuàng)建ThreadLocalMap(this, firstValue) void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
2.get()
public T get() { // 獲取當前線程 Thread t = Thread.currentThread(); // 獲取線程的成員變量threadLocals ThreadLocalMap map = getMap(t); if (map != null) { // 獲取hashMap的值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 如果為null return setInitialValue(); } private T setInitialValue() { // 初始化值為null T value = initialValue(); // 獲取當前線程 Thread t = Thread.currentThread(); // 獲取當前線程的threadLocals 成員變量 ThreadLocalMap map = getMap(t); // 如果數(shù)據(jù)為null則會存儲把這個ThreadLocal的對象在這個線程的值存儲為null if (map != null) map.set(this, value); else createMap(t, value); return value; } //初始化值 protected T initialValue() { return null; }
3. remove()
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocal不具備繼承性
從上面的set接口 和 get接口就不難看出我們現(xiàn)在存儲、獲取、刪除數(shù)據(jù)都是在當前線程threadLocals成員變量中存儲的。所以父線程和存儲的數(shù)據(jù)在子線程中獲取不了,在子線程中存儲的數(shù)據(jù)在父線程中存儲不了。
例:
public class ThreadLocalTest { static ThreadLocal<Integer> stringThreadLocal = new ThreadLocal<>(); static Integer num = 5; public static void main(String[] args) throws InterruptedException { new Thread(()->{ stringThreadLocal.set(num); System.out.println("線程1: " + stringThreadLocal.get()); new Thread(()->{ System.out.println("線程1的子線程: " + stringThreadLocal.get()); }).start(); }).start(); } }
結(jié)果:
InheritableThreadLocal類
為了解決上述問題,為了讓子類能夠訪問父類的數(shù)據(jù),則InheritableThreadLocal應(yīng)用而生。
首先InheritableThreadLocal類繼承了ThreadLocal類 并重寫了ThreadLocla的三個方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> { // 只返回父類線程的值 protected T childValue(T parentValue) { return parentValue; } // 獲取線程inheritableThreadLocals值 ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } // 創(chuàng)建當前線程的inheritableThreadLocals void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
再看下Thread創(chuàng)建的init方法
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ...... // 獲取父類線程 Thread parent = currentThread(); ........ // 如果 在創(chuàng)建子類線程的時候如果返現(xiàn)父類線程的數(shù)據(jù)不為null 則把父類線程的數(shù)據(jù)存儲到子類線程的 //inheritableThreadLocals成員變量中去 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }
這樣父類線程的inheritThreadLocals數(shù)據(jù)就存儲到了子類變量中去了。 值得注意的事,雖然能用父類的放進去的值,但是不影響父類的操作,也就是子類和父類的值是隔離開的。
package com.example.spring; public class ThreadLocalTest { static ThreadLocal<Integer> stringThreadLocal = new ThreadLocal<>(); static InheritableThreadLocal<Integer> integerInheritableThreadLocal = new InheritableThreadLocal(); static Integer num = 5; public static void main(String[] args) throws InterruptedException { new Thread(()->{ integerInheritableThreadLocal.set(num); System.out.println("線程1: " + integerInheritableThreadLocal.get()); new Thread(()->{ integerInheritableThreadLocal.set(4); System.out.println("線程1的子線程: " + integerInheritableThreadLocal.get()); }).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("線程1的最終結(jié)果:" + integerInheritableThreadLocal.get()); }).start(); } }
到此這篇關(guān)于Java線程變量ThreadLocal詳細解讀的文章就介紹到這了,更多相關(guān)Java線程變量ThreadLocal內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Mybatis框架增刪查改與核心配置詳解流程與用法
MyBatis 是一款優(yōu)秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO為數(shù)據(jù)庫中的記錄2021-10-10java實現(xiàn)二維數(shù)組轉(zhuǎn)json的方法示例
這篇文章主要介紹了java實現(xiàn)二維數(shù)組轉(zhuǎn)json的方法,涉及java數(shù)組遍歷及json格式數(shù)據(jù)構(gòu)造相關(guān)操作技巧,需要的朋友可以參考下2017-10-10