电子商务网站建设的实训报告,生成链接的网站,c#网站开发+pdf,广东省会计信息服务平台关于ThreadLocal#xff0c;可能很多同学在学习Java的并发编程部分时#xff0c;都有所耳闻#xff0c;但是如果要仔细问ThreadLocal是个啥#xff0c;我们可能也说不清楚#xff0c;所以这篇博客旨在帮助大家了解ThreadLocal到底是个啥#xff1f; 1.ThreadLocal是什么可能很多同学在学习Java的并发编程部分时都有所耳闻但是如果要仔细问ThreadLocal是个啥我们可能也说不清楚所以这篇博客旨在帮助大家了解ThreadLocal到底是个啥 1.ThreadLocal是什么
首先我们要知道的是ThreadLocal类位于Java标准库的java.lang包中它是Java中的一个类我们可以用它来声明一个ThreadLocal变量如下
ThreadLocalString local new ThreadLocal(); 好的接下来解释一下ThreadLocal的含义
它从名字上看叫做本地线程变量意思是说ThreadLocal中填充的的是当前线程的变量该变量对其他线程而言是封闭且隔离的ThreadLocal为变量在每个线程中创建了一个副本这样每个线程都可以访问自己内部的副本变量。
相信上面的文字描述大家会不太理解简单来说就是用ThreadLocal创建的变量我们可能会在不同的线程中用到那么为了避免线程安全问题每个线程都会为自己单独存一份这个变量并且单独使用和修改这个变量这样不同的线程之间就各自使用各自的ThreadLocal变量互不影响。
但是到底具体每个线程是怎样存储的这个变量以及这个变量如何被这个线程调用这些的底层是如何实现的请接着向下看。 2.举例
大家先看一个例子
public class ThreadLocalTest02 {public static void main(String[] args) {
//创建一个ThreadLocal变量名为localThreadLocalString local new ThreadLocal();
//创建10个线程并且每次都在不同的线程中加入这个local变量IntStream.range(0, 10).forEach(i - new Thread(() - {
//使用set方法设置加入的local内容local.set(Thread.currentThread().getName() : i);
//然后输入当前线程存储的local变量的信息System.out.println(线程 Thread.currentThread().getName() ,local: local.get());}).start());}
}
结果如下
输出结果
线程Thread-0,local:Thread-0:0
线程Thread-1,local:Thread-1:1
线程Thread-2,local:Thread-2:2
线程Thread-3,local:Thread-3:3
线程Thread-4,local:Thread-4:4
线程Thread-5,local:Thread-5:5
线程Thread-6,local:Thread-6:6
线程Thread-7,local:Thread-7:7
线程Thread-8,local:Thread-8:8
线程Thread-9,local:Thread-9:9
上面的结果说明了什么呢我们在每个线程中都添加了local对象并且内容是不同的然后我们再使用get方法输出local的值。我们发现我们只使用了一个local对象但是在十个线程中的值都是不同的而且它们的值不会相互影响这就是ThreadLocal的简单应用。不同的线程对这个local对象有着自己的备份。 3.Set方法
请大家先仔细阅读一下下面这段源码逻辑一点也不难我加了注释
public class ThreadLocalT {public void set(T value) {
//先获取当前线程例如在线程1中调用了local.set方法那么这个t就是线程1Thread t Thread.currentThread();//然后获取当前线程1中的ThreadLocalMapThreadLocalMap map getMap(t);//如果map为空说明此线程还没有存入任何一个ThreadLocal对象我们就创建一个ThreadLocalMap
//如果map不为空那么我们就直接将value存入这个ThreadLocalMap中if (map ! null)map.set(this, value);elsecreateMap(t, value);}大家现在可能会有疑惑什么是ThreadLocalMap啊为啥是从当前线程中获取啊还有createMap方法到底是干啥的捏我们一一进行解释
什么是ThreadLocalMap
我们刚才上面说我们每个线程都会存储ThreadLocal对象的备份那么存储在哪里呢答案就是在ThreadLocalMap中ThreadLocalMap为 ThreadLocal的一个静态内部类里面定义了Entry来保存数据那么既然是map就会有键值对的结构键的位置存的就是我们的ThreadLocal对象而值存储的就是通过set方法存入的那个值例如这一句代码local.set( i);那么存到这个线程中的ThreadLocalMap的一个entry中键和值就分别是 locali 接下来我们就看一下ThreadLocalMap它是ThreadLocal的一个内部类还有Entry的结构 // 内部类ThreadLocalMapstatic class ThreadLocalMap {static class Entry extends WeakReferenceThreadLocal? {Object value;// 内部类Entry实际存储数据的地方// Entry的key是ThreadLocal对象不是当前线程ID或者名称Entry(ThreadLocal? k, Object v) {super(k);value v;}}// 注意这里维护的是Entry数组private Entry[] table;}
可以看出实际上存储数据的是Entry而TheadLocalMap则是一个Entry数组
ok了解了这个结构后我们又回到宏观的角度看待一下问题刚才说每个线程都有自己的备份并且这些备份是当前线程独有的那么既然上面说存储数据的是ThreadLocalMap并且每个线程都有自己的独有的一份那么这个ThreadLocalMap到底存在哪里呢答案就是ThreadLocalMap是作为Thread类的一个私有属性实现的这样就可以保证每个Thread线程都有自己独一份的TheadLocalMap来存储自己的Threadlocal变量。
public class Thread {/* ... 省略其他代码 ... *//*** ThreadLocalMap实例用于存储ThreadLocal变量的键值对*/ThreadLocal.ThreadLocalMap threadLocals null;/* ... 省略其他代码 ... */
} 好好好现在我们算是知道了原来为每个线程存储这些ThreadLocal变量的就是Thread类中的属性threadLocals
那么这样我们就能解释为什么要从线程中获取map了看一下刚才的set方法中的这一句我们就知道为啥要从线程中获取了
//然后获取当前线程1中的ThreadLocalMapThreadLocalMap map getMap(t);
接着就是后面的代码相信大家也就能明白为什么要这样写啦
//如果map为空说明此线程还没有存入任何一个ThreadLocal对象我们就创建一个ThreadLocalMap
//如果map不为空那么我们就直接将value存入这个ThreadLocalMap中if (map ! null)map.set(this, value);elsecreateMap(t, value);}这是createMap方法看到它给什么赋值吗就是我们刚才说的Thread线程类中的那个存储ThreadLocalMap的属性哦~ void createMap(Thread t, T firstValue) {t.threadLocals new ThreadLocalMap(this, firstValue);}okset方法就介绍到这里了 4.get方法
get方法的作用很简单它通过local对象调用返回当前线程的以local对象为键对应的那个值即可例如 System.out.println(local.get());就是输出当前线程的local对象当时通过set方法存入的值。 get方法的源码如下 public T get() {
//先获取当前调用get方法的线程Thread t Thread.currentThread();//然后获取此线程的ThreadLocalMap对象这里面存储着local键值对ThreadLocalMap map getMap(t);/*如果map不为空就在map里面寻找键为this的entry为什么是this呢因为当前类
是ThreadLocal类而get方法通过local.get()的方式调用所以这里的this就指的
是这个local对象也就是entry的键。如果找的了这个以local为键的entry我们就
返回对应的值即可。
*/if (map ! null) {ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {SuppressWarnings(unchecked)T result (T)e.value;return result;}}return setInitialValue();}
okThreadLocal的具体应用和get方法就介绍到这里 5.ThreadLocal的结构
有了上面的基础我们现在来看一下他在内存中的结构 6.内存泄漏问题
仔细看下ThreadLocal内存结构就会发现Entry数组对象通过ThreadLocalMap最终被Thread持有并且是强引用。也就是说Entry数组对象的生命周期和当前线程一样。即使ThreadLocal对象被回收了Entry数组对象也不一定被回收这样就有可能发生内存泄漏。ThreadLocal在设计的时候就提供了一些补救措施
Entry的key是弱引用的ThreadLocal对象很容易被回收导致key为null但是value不为null。所以在调用get()、set(T)、remove()等方法的时候会自动清理key为null的Entity。remove()方法就是用来清理无用对象防止内存泄漏的。所以每次用完ThreadLocal后需要手动remove()。
解决办法使用完ThreadLocal后执行remove操作避免出现内存溢出情况。
如同 lock 的操作最后要执行解锁操作一样ThreadLocal使用完毕一定记得执行remove 方法清除当前线程的数值。如果不remove 当前线程对应的VALUE ,就会一直存在这个值。 这里复习一下对象的强引用、软引用、弱引用 1.强引用 我们平日里面的用到的new了一个对象就是强引用例如 Object obj new Object();当JVM的内存空间不足时宁愿抛出OutOfMemoryError使得程序异常终止也不愿意回收具有强引用的存活着的对象 2.软引用 当JVM认为内存空间不足时就回去试图回收软引用指向的对象也就是说在JVM抛出OutOfMemoryError之前会去清理软引用对象。 3.弱引用 在GC的时候不管内存空间足不足都会回收这个对象同样也可以配合ReferenceQueue 使用也同样适用于内存敏感的缓存。ThreadLocal中的key就用到了弱引用。 7.最后我们还要知道为什么要使用ThreadLocal
ThreadLocal类在多线程编程中有几个重要的用途和优势 线程隔离ThreadLocal提供了一种将数据与线程关联的机制。通过使用ThreadLocal可以为每个线程创建独立的变量副本使得每个线程都可以独立地访问和修改自己的变量副本而不会干扰其他线程的数据。这样可以实现线程间的数据隔离避免了线程安全问题。 状态传递ThreadLocal可以用于在同一个线程的多个方法之间传递状态信息而无需在方法参数中显式传递。通过将状态信息存储在ThreadLocal变量中不同的方法可以通过ThreadLocal访问和修改共享的状态而无需显式传递参数。这样可以简化方法的调用提高代码的可读性和可维护性。 线程上下文管理有些情况下需要在整个线程执行期间共享某些上下文信息比如用户认证信息、数据库连接等。通过将这些信息存储在ThreadLocal中可以在同一线程的任何地方方便地访问和使用这些信息而无需显式传递或在每个方法中重复获取。 避免锁竞争在某些情况下使用ThreadLocal可以避免使用锁来同步对共享变量的访问。由于每个线程都有自己的变量副本线程之间不会产生竞争条件从而避免了锁竞争和同步开销提高了程序的性能。