Thread和ThreadLocal、ThreadLocalMap的关系
ThreadLocal是什么
ThreadLocal官方注释:
翻译过来大致意思是:ThreadLocal可以提供局部变量,通过set和get方法对局部变量进行操作,并且局部变量是每个线程独立的、数据隔离的。ThreadLocal通常作为线程的私有的静态变量,用于和UserId、事务Id相关联。
set方法:
public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); //如果ThreadLocalMap不是空则直接把当前ThreadLocal作为key存到map中 if (map != null) map.set(this, value); else //如果ThreadLocalMap是空,就初始化map createMap(t, value); }
getMap方法:
//获取ThreadLocalMap就是获取当前线程的threadLocals属性 ThreadLocal.ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
createMap方法:
void createMap(Thread t, T firstValue) { //新增一个ThreadLocalMap,把当前ThreadLocal作为key存到map中 //并且把新增的ThreadLocalMap作为当前线程的threadLocals属性 t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue); }
get方法:
public T get() { //获取当前线程 Thread t = Thread.currentThread(); //获取ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); //如果ThreadLocalMap不为空就通过当前ThreadLocal对象获取Entry,返回Entry的值 if (map != null) { ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T) e.value; return result; } } //如果ThreadLocalMap是空的就进行初始化 return setInitialValue(); }
setInitialValue方法:
private T setInitialValue() { //初始化value,实际是null T value = initialValue(); //把刚初始化的null值set到ThreadLocalMap中 Thread t = Thread.currentThread(); ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); //返回value return value; }
initialValue方法:
protected T initialValue() { return null; }
总结:ThreadLocal是多线程用来保存局部变量的一个类,而且保存的变量是具有隔离性的,线程独立的!
ThreadLocalMap是什么
通过ThreadLocal的set和get方法可以看到一个非常重要的类:ThreadLocalMap。实际上ThreadLocal的set和get方法都是通过这个map在操作数据,并且多线程Thread类的保存变量的属性就是ThreadLocalMap,所以它是连接ThreadLocal和Thread的桥梁。
那么ThreadLocalMap到底是什么?
ThreadLocalMap是ThreadLocal的一个静态内部类,它内部还有一个Entry静态内部类,所以ThreadLocal对数据的操作实际上是ThreadLocalMap,而ThreadLocalMap的操作又是由Entry来完成的。
/** * Entry继承了弱引用,通常把ThreadLocal对象作为key。 * 注意:当entry.get()==null时说明这个key没有被引用了,是可以被回收的 */ static class Entry extends WeakReference<ThreadLocal<?>> { /** * 这个值是与ThreadLocal相关联的value */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMap的关键方法:
set方法
private void set(ThreadLocal<?> key, Object value) { //table就是内部维护的Entry数组,用来存数据 Entry[] tab = table; int len = tab.length; //根据ThreadLocal的哈希值和Entry数组的长度-1进行逻辑运算,算出数据下标 int i = key.threadLocalHashCode & (len - 1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //如果key相同就直接替换value if (k == key) { e.value = value; return; } //如果e.get()为空则说明之前的Entry没有相关引用了(被称为StaleEntry),所以进行更新当前节点的Entry if (k == null) { replaceStaleEntry(key, value, i); return; } } //走到这说明在i的下标处没有数据,所以直接新增一个Entry tab[i] = new Entry(key, value); int sz = ++size; //cleanSomeSlots方法是清除被称为StaleEntry //如果cleanSomeSlots方法返回false即没有StaleEntry并且当前数据量大于阈值时进行rehash if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
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 //走到这有两种情况:1、Entry不同但是key相同,直接返回;2、key为null的stale节点,进行清除操作 return getEntryAfterMiss(key, i, e); }
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)]) { //先清除节点然后再调用清除staleEntry的方法防止内存泄漏 if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
ThreadLocal的简单使用
多线程操作普通引用对象时会存在数据安全问题:
public class MyThread1 { static String str = "000"; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { long id = Thread.currentThread().getId(); MyThread1.str = String.valueOf(id); System.out.println("t1:" + MyThread1.str); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { long id = Thread.currentThread().getId(); MyThread1.str = String.valueOf(id); System.out.println("t2:" + MyThread1.str); } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { long id = Thread.currentThread().getId(); MyThread1.str = String.valueOf(id); System.out.println("t3:" + MyThread1.str); } }); t1.start(); t2.start(); t3.start(); } }
结果:
t2:12 t3:13 t1:12
使用ThreadLocal来保证数据隔离:
public class MyThread2 { static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { long id = Thread.currentThread().getId(); MyThread2.threadLocal.set(String.valueOf(id)); System.out.println("t1:" + MyThread2.threadLocal.get()); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { long id = Thread.currentThread().getId(); MyThread2.threadLocal.set(String.valueOf(id)); System.out.println("t2:" + MyThread2.threadLocal.get()); } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { long id = Thread.currentThread().getId(); MyThread2.threadLocal.set(String.valueOf(id)); System.out.println("t3:" + MyThread2.threadLocal.get()); } }); t1.start(); t2.start(); t3.start(); } }
结果:
t1:11 t2:12 t3:13
Thread和ThreadLocal、ThreadLocalMap的关系
Thread和ThreadLocal:ThreadLocal保证每个Thread的数据是隔离的。
Thread和ThreadLocalMap:Thread类有个成员变量是ThreadLocalMap,并以ThreadLocal为key来存储数据。
ThreadLocal和ThreadLocalMap:ThreadLocalMap是ThreadLocal的内部类,实际上ThreadLocal对数据的操作是通过ThreadLocalMap进行的。
补充:关于ThreadLocal如何解决内存泄漏的
什么是'内存泄漏'?
内存泄漏是指在编程过程中,程序动态分配的内存资源因为某些原因没有被正确地释放或回收,从而导致这些内存资源持续占据系统内存并逐渐积累,最终可能耗尽系统内存,影响程序的正常运行甚至是系统崩溃。内存泄漏不同于物理内存的消失,而是涉及到虚拟内存的使用情况。在Java开发中,通常所说的内存泄漏特指JVM(Java虚拟机)内存的管理问题。内存泄漏的原因可能是因为程序员在申请和分配内存后忘记释放,或者是因为内存管理机制的问题,如垃圾收集器无法有效地处理不再使用的内存。简而言之,内存泄漏是由于内存资源的未释放导致的系统内存浪费现象。
什么是‘弱引用’?
强引用(Strong Reference)和弱引用(Weak Reference)是两种不同的引用类型,它们的主要区别在于对象在被垃圾回收时的条件和时机。
- 强引用 通常是通过 `new` 关键字创建的对象,它在对象被创建时就建立了引用关系。强引用使得垃圾回收器不会主动回收具有强引用的对象,即使在内存不足的情况下也不会触发垃圾回收。这是因为强引用认为对象是不可或缺的,因此垃圾回收器不会在没有强引用的情况下回收它。
- 弱引用 与强引用相对,它是另一种较为宽松的引用方式。弱引用不会阻止垃圾回收器回收它所引用的对象,但是当垃圾回收器开始工作时,它会首先检查所有强引用为空的对象。如果发现只具有弱引用的对象,垃圾回收器会将其回收,无论当前内存是否充足。
总结来说,强引用是一种非常紧密的引用,几乎相当于对象不可缺少的一部分;而弱引用则是更加灵活,但在没有强引用保护的情况下,它的对象可能会被垃圾回收器回收。
ThreadLocal是如何解决内存泄漏的?
1、采用弱引用。ThreadLocal对数据的操作实际上是ThreadLocalMap中Entry节点对数据的操作,而ThreadLocalMap中的Entry又继承了WeakReference(弱引用),所以即使存在内存泄漏问题也会被JVM垃圾回收掉;
2、清除方法。ThreadLocalMap的set、get、remove方法中都包含对key是否为空的判断,如果为空则为‘StaleEntry’然后被清除掉。