ThreadLocal
主要解决多线程并发访问导致数据不一致问题。
ThreadLocal
为每一个使用该变量的线程都提供一个变量值的副本,虽然这种方式耗费内存,但是大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
举个例子:将 ThreadLocal
比喻成存放数据的盒子,盒子中只可以存储盒子主人的私有数据。由于数据空间是隔离的,因此,多线程并发访问而互不影响,从而避免了线程安全的问题。
ThreadLocal 是如何做到为每一个线程维护变量的副本?其实实现思路很简单,在 ThreadLocal
类中有一个 ThreadLocalMap
,用于存储每一个线程的变量的副本。
###ThreadLocal和Synchonized 的区别
ThreadLocal
:使用副本机制,使每个线程在某一时该访问到的对象是不同的,从而保证线程间的 数据隔离
。 synchronized
:使用锁机制,使对象在某一时只能被一个线程访问,从而保证线程间的 数据共享
。 import java.util.*; 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()); } } 复制代码
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
只是对其进行了封装,并传递变量值,通过 ThreadLocalMap
的 get()
方法获取当前线程局部变量的值。
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 再保存数据。
ThreadLocalMap
中使用的 key 为弱引用,而 value 是强引用。所以,如果 ThreadLocal
没有被外部强引用的情况下,在GC 执行的时候会把 key 清理掉,而 value 不会被清理。这样一来, ThreadLocalMap
中就会出现 key 为 null 的 Entry。假如不做任何措施的话,value 可能永远被 GC 回收,这个时候可能会产生内存泄漏。
以上情况, ThreadLocalMap
的实现已经考虑到了,在调用 set()
、 get()
和 remove()
方法时,会清理 key 为 null 的记录。(我们在使用完 ThreadLocal 后,最好手动调用 remove() 方法)
如果一个对象只具有弱引用,那就属于可有可无的对象。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。垃圾回收器线程执行时会扫描它所管辖的内存区域,一旦发现只具有弱引用的对象,不管当前内存空间足够与否,都会将它回收。不过,由于垃圾回收期是一个优先级很低的线程,因此不一定很快就会发现那些只具有弱引用的对象。
弱引用可以和一个引用队列( ReferenceQueue
)联合使用,如果弱引用对象被垃圾回收后,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。