目录
ThreadLocal的简介
ThreadLocal的实现原理
ThreadLocalMap详解
Entry的数据结构
set()方法
getEntry()方法
remove()方法
ThreadLocal的应用场景
ThreadLocal可以被理解为线程的本地变量。它提供了一种将变量与线程关联起来的机制,使得每个线程都可以拥有自己独立的变量副本,互不干扰。
在多线程编程中通常解决线程安全的问题,我们会利用synchronzed或者lock控制线程对临界区资源的同步顺序从而解决线程安全的问题,但是这种加锁的方式会让未获取到锁的线程进行阻塞等待,很显然这种方式的时间效率并不是很好。并且共享变量的访问往往是一个潜在的线程安全问题。通过使用ThreadLocal,我们可以为每个线程创建一个独立的变量副本,使得每个线程都可以独立地操作该变量,而不会对其他线程产生影响。这种方式实际上是通过空间换时间的方式,通过增加内存占用来避免线程间的竞争和同步机制。
ThreadLocal类实际上是一个容器,内部维护了一个ThreadLocalMap,其中的键值对以线程作为键,变量副本作为值。每个线程访问ThreadLocal变量时,实际上是在操作自己的变量副本,线程之间互不干扰。
需要注意的是,使用ThreadLocal时要注意及时清理不再使用的变量副本,以避免内存泄漏。通常在使用完ThreadLocal后,应该调用其remove方法移除当前线程的变量副本。
总结起来,ThreadLocal提供了一种简便的方式,使得每个线程都可以拥有自己独立的变量副本,实现线程间的数据隔离,从而避免了一些多线程环境下的竞争和同步问题。这是一种以空间换时间的策略,通过增加内存占用来提高程序的并发性能和线程安全性。
ThreadLocal的实现原理是通过每个线程维护一个ThreadLocalMap来实现。ThreadLocal是一个泛型类,通过调用set方法将值存放在当前线程的ThreadLocalMap中,以ThreadLocal实例为key,值为value进行存储。而get方法则是通过ThreadLocal实例作为key从ThreadLocalMap中获取对应的值。
具体实现原理如下:
1. ThreadLocal类中定义了一个静态内部类ThreadLocalMap,该类是ThreadLocal的一个成员变量,用于存储线程局部变量。
2. 在调用ThreadLocal的set方法时,会首先获取当前线程的ThreadLocalMap对象。如果ThreadLocalMap对象存在,则以当前ThreadLocal实例为key,将value存储在ThreadLocalMap中。如果ThreadLocalMap对象不存在,则创建一个新的ThreadLocalMap对象,并将其与当前线程关联。
3. 在调用ThreadLocal的get方法时,同样会先获取当前线程的ThreadLocalMap对象。然后通过ThreadLocal实例作为key,从ThreadLocalMap中取出对应的值。
4. 在调用ThreadLocal的remove方法时,会先获取当前线程的ThreadLocalMap对象,然后从ThreadLocalMap中删除以当前ThreadLocal实例为key的键值对。
总结一下,ThreadLocal的实现原理就是通过每个线程维护一个ThreadLocalMap,使用ThreadLocal实例作为key,将值存储在ThreadLocalMap中,实现了线程间的隔离和数据的独立访问。这样就保证了每个线程都可以独立地访问自己的ThreadLocal变量,而不会受到其他线程的干扰。
public class main {
// 创建一个ThreadLocal对象,用于存储线程局部变量
private static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 在主线程中设置ThreadLocal的值
threadLocal.set("MainValue");
// 创建两个子线程并启动
Thread thread1 = new Thread(new MyRunnable("Thread1"));
Thread thread2 = new Thread(new MyRunnable("Thread2"));
thread1.start();
thread2.start();
try {
// 等待两个子线程执行完毕
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取主线程中的ThreadLocal的值并打印
System.out.println("主线程中的ThreadLocal的值:" + threadLocal.get());
// 调用remove方法清除主线程中的ThreadLocal的值
threadLocal.remove();
// 再次获取主线程中的ThreadLocal的值并打印
System.out.println("主线程中的ThreadLocal的值:" + threadLocal.get());
}
// 自定义Runnable类,用于在每个线程中访问ThreadLocal变量
private static class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
// 在当前线程中设置ThreadLocal的值
threadLocal.set(name + "Value");
// 在当前线程中获取ThreadLocal的值并打印
System.out.println(name + "线程中的ThreadLocal的值:" + threadLocal.get());
// 调用remove方法清除当前线程中的ThreadLocal的值
threadLocal.remove();
// 再次获取当前线程中的ThreadLocal的值并打印
System.out.println(name + "线程中的ThreadLocal的值:" + threadLocal.get());
}
}
}
代码分析:
注意:使用了多线程来访问和设置ThreadLocal变量。由于多线程的执行顺序和调度是不确定的,因此每次运行的结果可能会不同。
因为在多线程环境下,每个线程都有自己的副本,称为线程局部变量。而ThreadLocal对象就是用来存储线程局部变量的。在代码中,主线程设置了threadLocal的值为"MainValue",而两个子线程分别设置了threadLocal的值为"Thread1Value"和"Thread2Value"。
由于线程调度的不确定性,不同的线程可能会以不同的顺序运行,这导致了每次运行的结果可能会不同。例如,在某一次运行中,主线程先执行,然后是Thread1线程,最后是Thread2线程;而在另一次运行中,可能是Thread2线程先执行,然后是Thread1线程,最后是主线程。
因此,每次运行的输出结果取决于各个线程的执行顺序和调度情况,所以会出现不一样的结果。
ThreadLocalMap是ThreadLocal的核心实现类,用于存储线程局部变量。ThreadLocalMap是一个自定义的哈希表,它使用了开放地址法来解决哈希冲突。每个Thread对象都有自己的ThreadLocalMap对象,用于存储线程局部变量。
ThreadLocalMap中的每个元素都是一个Entry对象,Entry包含了线程局部变量的键值对,其中键是ThreadLocal对象,值是线程局部变量的值。Entry内部还包含了一些相关的操作方法,例如get()方法获取线程局部变量的值,set()方法设置线程局部变量的值等。下面分别介绍Entry的数据结构、set()方法、getEntry()方法和remove()方法。
static class Entry extends WeakReference> {
/** 与此 ThreadLocal 关联的值。*/
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
可以看到,Entry类继承了WeakReference
public void set(ThreadLocal> key, Object value) {
// 获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(Thread.currentThread());
if (map != null)
// 如果ThreadLocalMap不为null,则将key和value放入map中
map.set(key, value);
else
// 如果ThreadLocalMap为null,则创建一个新的ThreadLocalMap对象,并将key和value放入其中
createMap(key, value);
}
set()方法用于设置线程局部变量的值,首先通过getMap(Thread.currentThread())方法获取当前线程的ThreadLocalMap对象,如果该对象不为null,则将键值对放入ThreadLocalMap中;否则,调用createMap(key, value)方法创建一个新的ThreadLocalMap对象,并将键值对放入其中。
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;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
getEntry()方法用于获取与指定ThreadLocal对象关联的Entry对象。首先通过key.threadLocalHashCode & (table.length - 1)计算哈希值,然后在table数组中查找对应位置上的Entry对象。如果找到的Entry对象不为null且其引用的ThreadLocal对象与指定ThreadLocal对象相同,则返回该Entry对象;否则,调用getEntryAfterMiss(key, i, e)方法在table数组中的其他位置继续查找。
如果在查找的过程中发现Entry对象的引用为null,则需要调用expungeStaleEntry(i)方法将table数组中对应位置的Entry对象置为null。如果最终仍然没有找到与指定ThreadLocal对象关联的Entry对象,则返回null。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
remove()方法用于清除当前线程中指定ThreadLocal对象的值。首先通过getMap(Thread.currentThread())方法获取当前线程的ThreadLocalMap对象,如果该对象不为null,则调用m.remove(this)方法将指定ThreadLocal对象的值清除掉;否则,不做任何操作。
ThreadLocal的主要作用是提供线程局部变量,即每个线程都拥有自己的变量副本,互相之间不会相互干扰。 ThreadLocal 的使用场景如下:
需要注意的是,在使用ThreadLocal时,要避免内存泄漏问题。因为ThreadLocal弱引用的特性,如果没有及时清理线程的引用,可能会导致对象无法回收,造成内存泄漏。因此,在不再使用ThreadLocal时,要调用remove()方法主动清除数据,或者使用ThreadLocal的initialValue()方法设置初始值,以确保资源能够正确释放。