多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的另一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
public class Main {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
Main main = new Main();
ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
pool.execute(() -> {
main.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "--->" + main.getContent());
});
}
pool.shutdown();
}
}
发生这样的情况是因为在setContent()和getContent()方法之间有可能会被其他线程插队
public class Main {
private String content;
public final Object lock = new Object();
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
Main main = new Main();
ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
pool.execute(() -> {
synchronized (main.lock) {
main.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "--->" + main.getContent());
}
});
}
pool.shutdown();
}
}
public class Main {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
Main main = new Main();
ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
pool.execute(() -> {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
main.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "--->" + main.getContent());
} finally {
lock.unlock();
}
});
}
pool.shutdown();
}
}
public class Main {
private String content;
ThreadLocal<String> threadLocal = new ThreadLocal<>();
public String getContent() {
// return this.content;
return threadLocal.get();
}
public void setContent(String content) {
// this.content = content;
threadLocal.set(content);
}
public void removeThreadLocal() {
threadLocal.remove();
}
public static void main(String[] args) {
Main main = new Main();
ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
pool.execute(() -> {
main.setContent(Thread.currentThread().getName() + "的数据");
System.out.println(Thread.currentThread().getName() + "--->" + main.getContent());
});
}
pool.shutdown();
main.removeThreadLocal();
}
}
二者处理问题的角度不同。
这样的改变有什么好处?
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
// map不为空,设置 <K, V>
map.set(this, value);
else
// map为空,创建一个ThreadLocalMap并将 K, V 放入map中
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
initialValue()
函数获取初始值value,然后用当前ThreadLocal对象的引用作为key,自定义初始值value作为value创建一个新的ThreadLocalMap获取当前线程的ThreadLcoalMap进而获取Entry,如果存在则返回值,不存在则返回initialValue()
指定的初始值
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// map不为空,尝试获取ThreadLocalMap中当前ThreadLocal对象做key的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果获取到了,返回value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 初始化
// 1.ThreadLocalMap不存在,表示此线程没有维护的ThreadLocalMap对象
// 2.ThreadLocalMap存在,但是没有找到以当前ThreadLocal对象做key的Entry
return setInitialValue();
}
private T setInitialValue() {
// 调用initialValue()获取初始化的值
// 子类不重写,默认为null
T value = initialValue();
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
// 存在,设置Entry
map.set(this, value);
else
// 不存在,创建一个ThreadLocalMap并设置Entry
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
删除当前线程中保存ThreadLcoal的Entry
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
此方法的作用是返回该线程局部变量的初始值
set()
方法还未调用而先调用了get()
方法时才执行,并且仅执行1次protected T initialValue() {
return null;
}
ThreadLocalMap是ThreadLocal的静态内部类,没有实现Map接口,它的Map功能是独立实现的,这个Map解决哈希冲突用的线性探测法(将Entry数组看作一个环形数组,循环探测)而非JDK的Map用的拉链法,内部的Entry也是独立实现的。
// 初始容量,必须是2^n
private static final int INITIAL_CAPACITY = 16;
// 存放数据的table,数组长度必须为2^n
private Entry[] table;
// 数组里面Entry的个数,用于判断table当前使用量是否超过阈值
private int size = 0;
// 进行扩容的阈值,默认为0,数组中的Entry个数大于它的时候需要扩容
private int threshold;
// Entry是ThreadLocalMap的静态内部类
// key是弱引用WeakReference,目的是让ThreadLocal对象的生命周期与线程(Thread对象)生命周期进行解绑
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
// key必须为ThreadLocal对象
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
假设在业务中使用完ThreadLocal对象了,ThreadLcoal Ref被回收了,但是由于Entry仍然具有对堆中ThreadLocal对象的引用:
如果Entry的key对ThreadLocal对象的引用是强引用,在没有调用ThreadLocal对象的remove()
方法以及当前线程仍然还在运行的前提下,始终有着强引用链Thread Ref -> Thread Obj -> ThreadLocalMap Obj -> Entry Obj
,导致Entry无法被回收,进而导致内存泄漏。
如果Entry的key对ThreadLocal对象的引用是弱引用(绿色虚线),ThreadLcoal Ref被回收后,由于没有任何强引用连接,只有一个弱引用连接,那么堆中的ThreadLocal对象就可以被顺利回收,Entry中的key变为null。但是在没有调用ThreadLocal对象的remove()
方法以及当前线程仍然还在运行的前提下,依然有着强引用链Thread Ref -> Thread Obj -> ThreadLocalMap Obj -> Entry Obj -> value Obj
,导致Entry的value对象无法被回收,进而导致内存泄漏。
可见,即便Entry的key为弱引用,还是有可能会发生内存泄漏的。因此,内存泄露产生的原因不在于Entry的key是强引用还是弱引用。
ThreadLcoal内存泄漏的根本原因是ThreadLocalMap对象的生命周期跟Thread对象一样长,导致ThreadLocalMap底层的Entry数组中的Entry对象的value(在不调用remove()
方法和线程运行完成之前)无法被回收。
使用弱引用会将不用的ThreadLocal对象及时回收,对应的Entry的key会值为null,而在ThreadLocalMap的set()
(replaceStaleEntry()
)、getEntry()
(expungeStaleEntry()
)、remove()
(expungeStaleEntry()
)方法中对会对key为null而value不为null的Entry进行处理(replaceStaleEntry()
方法中,如果key为null,则将value置为null,在将这个Entry数组的对应位置替换为一个新的Entry;expungeStaleEntry()
方法中,如果key为null,则将value置为null,再将这个Entry所在的数组的这个格子置为null)。
set()
和remove()
需要成对执行因为ThreadLocal一般都是配合线程池来用的,线程池资源不释放,导致这引用链一直存在没法释放value资源,进而导致内存溢出
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_43613793/article/details/120617208
内容来源于网络,如有侵权,请联系作者删除!