目录
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()方法设置初始值,以确保资源能够正确释放。