Java ThreadLocal

本文详细介绍了Java中的ThreadLocal类,包括其使用场景、创建和访问ThreadLocal变量的方法,以及如何初始化ThreadLocal变量的值。同时,文章深入分析了ThreadLocal的工作原理,包括其在Thread对象中的实现方式和如何解决Hash冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

**

一:ThreadLocal的简要介绍及使用

**

Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。

ThreadLocal的常见用法:

  • 存储单个线程上下文信息。比如存储id等;
  • 使变量线程安全。变量既然成为了每个线程内部的局部变量,自然就不会存在并发问题了;
  • 减少参数传递。比如做一个trace工具,能够输出工程从开始到结束的整个一次处理过程中所有的信息,从而方便debug。由于需要在工程各处随时取用,可放入ThreadLocal。(如果想要当前线程的子线程共享父线程的变量,可以使用InheritableThreadLocal)

如何创建ThreadLocal变量

以下代码展示了如何创建一个ThreadLocal变量:

private ThreadLocal my = new ThreadLocal();

我们可以看到,通过这段代码实例化了一个ThreadLocal对象。我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的set()方法设置的值。即使是两个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值。

如何访问ThreadLocal变量

一旦创建了一个ThreadLocal变量,你可以通过如下代码设置某个需要保存的值:

my.set("A thread local value”);

可以通过下面方法读取保存在ThreadLocal变量中的值:

String val = (String) my.get();

get()方法返回一个Object对象,set()对象需要传入一个Object类型的参数。

为ThreadLocal指定泛型类型

我们可以创建一个指定泛型类型的ThreadLocal对象,这样我们就不需要每次对使用get()方法返回的值作强制类型转换了。下面展示了指定泛型类型的ThreadLocal例子:

private ThreadLocal my = new ThreadLocal<String>();

现在我们只能往ThreadLocal对象中存入String类型的值了。

并且我们从ThreadLocal中获取值的时候也不需要强制类型转换了。

如何初始化ThreadLocal变量的值

由于在ThreadLocal对象中设置的值只能被设置这个值的线程访问到,线程无法在ThreadLocal对象上使用set()方法保存一个初始值,并且这个初始值能被所有线程访问到。

但是我们可以通过创建一个ThreadLocal的子类并且重写initialValue()方法,来为一个ThreadLocal对象指定一个初始值。这样就可以所有线程共享这个初始化值。代码如下:

ThreadLocal<String> my = new ThreadLocal<String>() {
			
			@Override
			protected String initialValue() {
				return "init val.";
			}
		};

一个完整的ThreadLocal例子

public class Demo03 {
	
	
	public static void main(String[] args) throws InterruptedException {
		
		Runnable runnable = new Runnable() {
			
			private ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
				@Override
				protected String initialValue() {
					return "init val.";
				}
			};
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName()+threadLocal.get());
				
				String val = new Random().nextDouble()+"";
				
				System.out.println(Thread.currentThread().getName()+val);
				
				threadLocal.set(val);
				
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName()+threadLocal.get());
			}
		};
		
		for(int i=1;i<10;i++) {
			new Thread(runnable).start();
		}
	}
}

关于InheritableThreadLocal

InheritableThreadLocal类是ThreadLocal类的子类。ThreadLocal中每个线程拥有它自己的值,与ThreadLocal不同的是,InheritableThreadLocal允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。

【注:所有子线程都会继承父线程保存的ThreadLocal值】

**

二:ThreadLocal的原理

**

ThreadLocal的源码分析

1.每个Thread对象内部维护一个ThreadLocalMap这样的一个<key=ThreadLocal对象,value=要存储的value> Map。

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

2.当我们调用ThreadLocal对象的get()方法时,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,则取出ThreadLocal的value,否则进行初始化并返回

	private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
	public static void main(String[] args) {
		threadLocal.get();
		threadLocal.set("set值");
	}

public T get() {
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap 引用:return t.threadLocals;
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
            	//当前ThreadLocalMap不为空,直接取值并返回
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //ThreadLocalMap为空,则进行初始化,并返回
        return setInitialValue();
    }


3.当我们调用set方法时,直接将键值对放入Map即可(Map为空则先创建)

public void set(T value) {
    Thread t = Thread.currentThread();
    //获取当前线程的ThreadLocalMap 引用
    ThreadLocalMap map = getMap(t);
    //map不为空,直接设值,为空则创建map
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

4.需要注意的是,ThreadLocalMap的Entry,维护了ThreadLocal的弱引用,当调用ThreadLocal的remove()方法时,会清除当前线程的当前ThreadLocal的Entry,垃圾回收就会回收Entry的对应的value对象。

为防止内存泄漏,ThreadLocal需要手动去释放资源

//ThreadLocal.ThreadLocalMap.Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
  /** The value associated with this ThreadLocal. */
       Object value;

       Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
       }
   }

public void testThreadLocal() {
	ThreadLocal<String> threadLocal = new ThreadLocal<String>();
	
	//当ThreadLocal不再使用后,为防止内存泄漏,建议手动清空
	
	//方式1:清除当前ThreadLocal在ThreadLocalMap中的键值对(Entry)
	threadLocal.remove();
	
	//方式2:将ThreadLocal引用置为null
	threadLocal = null;
}

拓展:

ThreadLocalMap的Hash冲突怎么解决?

Hash冲突怎么解决
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。

/**
 * Increment i modulo len.
 */
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

/**
 * Decrement i modulo len.
 */
private static int prevIndex(int i, int len) {
    return ((i - 1 >= 0) ? i - 1 : len - 1);
}

另一个写的较好的博文:
https://blog.youkuaiyun.com/qq_23315711/article/details/78642171

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值