Python 官方文档:入门教程 => 点击学习
目录1.了解ThreadLocal简介使用2.源码解析 – 探究实现思路threadLocals变量与ThreadLocalMapset(T value) 方法get() 方法rem
分享一下最近看的ThreadLocal的源码的一些体会。
@Slf4j
public class ThreadLocalTest {
static ThreadLocal<String> localStr = new ThreadLocal<>();
public static void main(String[] args) {
List<Thread> list = new LinkedList<>();
for(int i = 0; i < 1000; i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
localStr.set(Thread.currentThread().getName() + " localStr");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(localStr.get());
}
}, "t" + String.valueOf(i));
list.add(t);
}
for (Thread t : list) {
t.start();
}
}
}
而对于普通变量来说,很明显是存在线程安全问题的:
@Slf4j
public class ThreadLocalTest {
static ThreadLocal<String> localStr = new ThreadLocal<>();
static String shareStr;
public static void main(String[] args) {
List<Thread> list = new LinkedList<>();
for(int i = 0; i < 1000; i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
shareStr = Thread.currentThread().getName() + " shareStr";
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(shareStr);
}
}, "t" + String.valueOf(i));
list.add(t);
}
for (Thread t : list) {
t.start();
}
}
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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;
}
protected T initialValue() {
return null;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
InheritableThreadLocal英语翻译一下就是可继承的ThreadLocal,让我们看下它和ThreadLocal的继承性体现在哪。
这里的继承性指的是:子线程是否能访问父线程的变量。
threadLocals是当前线程的成员变量,在子线程中不可见
@Slf4j
public class InheritableThreadLocalTest {
static ThreadLocal<String> localStr = new ThreadLocal<>();
public static void main(String[] args) {
localStr.set("main线程为其设置的值");
new Thread(new Runnable() {
@Override
public void run() {
log.debug("访问localStr : " + localStr.get());
}
}).start();
System.out.println(localStr.get());
}
}
看一下InheritableThreadLocal的源码:
源码非常简短,下面简单分析一下:
从Thread的构造方法看,发现所有的构造方法都会调用init()方法进行初始化,init()方法有两个重载形式。
我们进入参数较多的init方法查看一下:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 新线程还未创建出来,当前线程就是即将要创建线程的父线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
// 如果父线程的inheritThreadLocals 不为空
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 设置子线程中的inheritableThreadLocals设置为父线程的inheritableThreadLocals
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}
我们重点看一下和inheritThreadLocals相关的地方(含注释的地方)
再看下ThreadLocal.createInheritedMap方法:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
看下这个构造方法:
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 调用inheritThreadLocals重写的childValue方法
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
通过上面的源码分析,可以发现,InheritableThreadLocal的继承性主要体现在:创建子线程时,会将父线程的inheritThreadLocals里面所有entry拷贝一份给子进程。
那么当子进程被创建出来之后,父进程又修改了inheritThreadLocals里面的值,这个操作是否对子线程可见,通过上面的源码可知,这个操作明显是不可见的,下面有个demo可以证实。
@Slf4j
public class InheritableThreadLocalTest {
static ThreadLocal<String> localStr = new ThreadLocal<>();
static InheritableThreadLocal<String> inheritableLocalStr = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
inheritableLocalStr.set("main线程第一次为inheritableLocalStr设置的值");
new Thread(new Runnable() {
@Override
public void run() {
log.debug("子线程第一次访问inheritableLocalStr : " + inheritableLocalStr.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("子线程第二次访问inheritableLocalStr : " + inheritableLocalStr.get());
}
}).start();
Thread.sleep(500);
inheritableLocalStr.set("main线程第二次为inheritableLocalStr设置的值");
log.debug("main线程第二次为inheritableLocalStr赋值");
Thread.sleep(1000);
}
}
看下输出:
可以发现,子线程创建出来后,对父线程中inheritThreadLocals的修改操作,对子线程不可见。
要充分理解ThreadLocal中存在的内存泄露问题,需要有以下JVM对内存管理的前置知识(这里篇幅问题就不补充了):
在分析上述ThreadLocalMap源码的时候,注意到有一个小细节,ThreadLocalMap的Entry继承了WeakReference<ThreadLocal<?>>,也就是说Entry的key是一个对ThreadLocal<?>的弱引用。问题来了,为什么这里要使用弱引用呢?
现在假设Entry的key是一个对ThreadLocal的强引用,当ThreadLocal对象使用完后,外部的强引用不存在,但是因为当前线程对象中的threadLocals还持有ThreadLocal的强引用,而threadLocals的生命周期是和线程一致的,这个时候,如果没有手动删除,整个Entry就发生了内存泄露。
现在假设Entry的key是一个对ThreadLocal的弱引用,当ThreadLocal对象使用完后,外部的强引用不存在,此时ThreadLocal对象只存在Entry中key对它的弱引用,在下次GC的时候,这个ThreadLocal对象就会被回收,导致key为null,此时value的强引用还存在,但是value已经不会被使用了,如果没有手动删除,那么这个Entry中的key就会发生内存泄露。
使用弱引用还有一些好处,那就是,当key为null时, ThreadLocalMap中最多存在一个key为null,并且当调用set(),get(),remove()这些方法的时候,是会清除掉key为null的entry的。
通过这个方法的源码可以看出,key为null的那个entry实际上迟早会被替换成新的entry。
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;
}
// 发现key为空
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
同理,可以看到在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);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
// 替换过时的entry
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
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)]) {
// 清除过时的key
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
ThreadLocal把数据存放到线程本地,解决了线程安全问题,没有使用锁,直接访问线程本地变量,效率较高(空间换时间。)
同时threadLocals的生命周期是和线程一致的,可以解决很多参数传递问题。
ATFWUS 2021-11-11
以上就是深入浅出解析Java ThreadLocal原理的详细内容,更多关于Java ThreadLocal原理的资料请关注编程网其它相关文章!
--结束END--
本文标题: 深入浅出解析Java ThreadLocal原理
本文链接: https://www.lsjlt.com/news/157031.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-03-01
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0