ThreadLocal
1. 概述
ThreadLocal
主要解决多线程并发访问导致数据不一致问题,它为每一个使用该变量的线程都提供一个变量值的副本,虽然这种方式耗费内存,但是大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
ThreadLocal 如何做到为每一个线程维护变量的副本?其实实现思路很简单,在ThreadLocal类中有一个
ThreadLocalMap
,用于存储每一个线程的变量的副本。
2. 实现原理
2.1 get()
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
可以理解为定制化的HashMap,而ThreadLocal只是对其进行了封装,并传递变量值,通过get()获取当前线程局部变量的值。
2.2 set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
实现原理和get()
一样,内部由ThreadLocalMap
管理数据,set时先检查map是否为空,如果不为空就直接保存数据,如果为空则先创建map再保存数据。
3. ThreadLocal内存泄露问题
ThreadLocalMap
中使用的key为弱引用,而value是强引用。所以,如果ThreadLocal
没有被外部强引用的情况下,在GC执行的时候会把key清理掉,而 value不会被清理。这样一来,ThreadLocalMap
中就会出现key为null的Entry。假如不做任何措施的话,value可能永远无法被GC回收,这个时候可能会产生内存泄漏。
以上情况,ThreadLocalMap
的实现已经考虑到了,在调用 set()
、get()
和remove()
方法时,会清理key为null的记录。(我们在使用完ThreadLocal后,最好手动调用remove()
)
4. ThreadLocal和synchonized的区别
-
ThreadLocal
:使用副本机制,使每个线程在同一时刻访问到的对象是不同的,因此能够保证多线程间的数据隔离。 -
synchronized
:使用锁机制,使对象在同一时刻只能被一个线程访问,因此保证多线程间的数据同步。
5. 使用示例
举例:将
ThreadLocal
比喻成存放数据的盒子,盒子中只可以存储盒子主人的私有数据。由于数据空间是隔离的,因此,多线程并发访问而互不影响,从而避免了线程安全的问题。
public class ThreadLocalDemo implements Runnable {
// list 不是线程安全的,所以每个线程都要有自己独立的副本
private static ThreadLocal<List> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ThreadLocalDemo obj = new ThreadLocalDemo();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(obj, "thread" + i);
Thread.sleep(1000);
t.start();
}
}
@Override
public void run() {
List<String> list = new ArrayList<>();
list.add(Thread.currentThread().getName());
threadLocal.set(list);
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
list.add("1");
list.add("2");
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
}
}
弱引用
如果一个对象只具有弱引用,那就属于可有可无的对象。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。垃圾回收器线程执行时会扫描它所管辖的内存区域,一旦发现只具有弱引用的对象,不管当前内存空间足够与否,都会将它回收。不过,由于垃圾回收期是一个优先级很低的线程,因此不一定很快就会发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(
ReferenceQueue
)联合使用,如果弱引用对象被垃圾回收后,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。