Java線程變量ThreadLocal詳細解讀
Java線程變量ThreadLocal
多線程訪問同一個變量的時候,很容易出現(xiàn)問題,特別是多線程對一個共享變量進行寫入的時候。為了線程的安全在進行數(shù)據(jù)寫入時候會進行數(shù)據(jù)的同步。

如上圖: 如果要想實現(xiàn)多個線程操作同一個數(shù)據(jù)而不被其他線程所影響,就必須加鎖操作,加鎖又增加了使用的復(fù)雜度。
ThredLocal的用處就是當(dāng)多個線程操作同一個變量的時候可以在創(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é)果:由上面的返回可以知道,當(dāng)在線程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) {
// 獲取當(dāng)前線程
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() {
// 獲取當(dāng)前線程
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();
// 獲取當(dāng)前線程
Thread t = Thread.currentThread();
// 獲取當(dāng)前線程的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ù)都是在當(dāng)前線程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)建當(dā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-10
java實現(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

