深圳网站建设 找猴王网络,网站描述如何写利于优化,域名查询阿里云,网站配资公司网站一、ThreadLocal简介
1、简介
ThreadLocal叫做线程变量#xff0c;它是一个线程的本地变量#xff0c;意味着这个变量是线程独有的#xff0c;是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。 即 ThreadLocal类用来提供线程内部的局部变量#xff0…一、ThreadLocal简介
1、简介
ThreadLocal叫做线程变量它是一个线程的本地变量意味着这个变量是线程独有的是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。 即 ThreadLocal类用来提供线程内部的局部变量不同的线程之间不会相互干扰。 ThreadLocal变量即线程局部变量同一个 ThreadLocal 所包含的对象在不同的 Thread 中有不同的副本。这里有几点需要注意
因为每个 Thread 内有自己的实例副本且该副本只能由当前 Thread 使用。这也是 ThreadLocal 命名的由来。既然每个 Thread 有自己的实例副本且其它 Thread 不可访问那就不存在多线程间共享的问题。
注意
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被 private static修饰。当一个线程Thread结束时它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用也即变量在线程间隔离而在方法或类间共享的场景。
2、ThreadLocal与Synchronized区别
ThreadLocal和Synchonized都用于解决多线程并发访问同一个资源对象的时候可能就会出现线程不安全的问题。
ThreadLocal是与一个线程绑定的本地变量也就意味着这个变量是线程独有的是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。加锁方式synchronized、Lock) 用于在多个线程间通信时能够获得数据共享。 但是ThreadLocal 这种解决多线程安全问题的方式与加锁方式synchronized、Lock) 是有本质的区别。
两者区别如下
1资源管理方面
Synchronized通过加锁的方式让多个线程之间逐一访问共享资源。ThreadLocal是每个线程都有一个资源副本是不需要加锁的。
2实现方式方面
锁是通过时间换空间的做法。 Synchronized是利用锁的机制使变量或代码块在某一时该只能被一个线程访问。ThreadLocal是通过空间换时间的做法。 ThreadLocal为每一个线程都提供了变量的副本使得每个线程在某一时间访问到的并不是同一个对象这样就隔离了多个线程对数据的数据共享。
3、使用场景
根据使用场景的不同我们可以选择不同的技术手段关键还是要看你的应用场景对于资源的管理是需要多线程之间共享还是单线程内部独享。
多线程之间共享资源使用加锁方式。单线程内部独享使用 ThreadLocal变量。
ThreadLocal 适用于如下两种场景
1每个线程需要有自己单独的实例。2实例需要在多个方法中共享但不希望被多线程共享。
二、ThreadLocal使用
使用ThreadLocal管理变量则每一个使用该变量的线程都获得该变量的副本副本之间相互独立这样每一个线程都可以随意修改自己的变量副本而不会对其他线程产生影响。
ThreadLocal类的常用方法
ThreadLocal threadLocal new ThreadLocal();创建ThreadLocal对象即一个线程本地变量。initialValue()返回此线程局部变量的当前线程的初始值 。set(T value)将此线程局部变量的当前线程副本中的值设置为value。。get()返回此线程局部变量的当前线程副本中的值 。remove()移除当前线程绑定的局部变量该方法可以帮助JVM进行GC。
1、示例1
public class ThreadLocalUseDemo1 {private static ThreadLocalString threadLocal1 new ThreadLocal();private static ThreadLocalInteger threadLocal2 new ThreadLocal();/*** 运行 count个线程,每个线程持有自己独有的 String类型编号*/public void startThreadArray(int count) {Thread[] runs new Thread[count];for (int i 0; i runs.length; i) {// 赋值编号idnew ThreadDemo1(i).start();}}/*** 线程类*/public static class ThreadDemo1 extends Thread {/*** 编号id*/private int codeId;public ThreadDemo1(int codeId) {this.codeId codeId;}Overridepublic void run() {String threadName Thread.currentThread().getName();threadLocal1.set(threadLocal1赋值线程_ codeId);if (codeId 2) {//如果是线程2设置 threadLocal2变量值乘以5threadLocal2.set(codeId * 5);}System.out.println(threadName -》 threadLocal1.get());System.out.println(threadName -》 threadLocal2.get());// 使用完移除help GCthreadLocal1.remove();threadLocal2.remove();}}public static void main(String[] args) {ThreadLocalUseDemo1 useDemo new ThreadLocalUseDemo1();// 启动3个线程useDemo.startThreadArray(3);}}从示例1中可以看到每个线程分别获取了自己线程存放的变量他们之间变量的获取并不会错乱。
2、示例2
public class ThreadLocalUseDemo2 {//public static ThreadLocalUseDemo2.Number number new ThreadLocalUseDemo2.Number(0);/*** 初始化 num值。使用时先通过get方法获取。*/public static ThreadLocalThreadLocalUseDemo2.Number threadLocalValue new ThreadLocalThreadLocalUseDemo2.Number() {Overrideprotected Number initialValue() {return new Number(0);}};/*** 数据类*/private static class Number {public Number(int num) {this.num num;}private int num;public int getNum() {return num;}public void setNum(int num) {this.num num;}Overridepublic String toString() {return Number [num num ];}}/*** 线程类*/public static class ThreadDemo2 extends Thread {Overridepublic void run() {// 如果没有初始化注意NPE。// static修饰的 number时注释掉这句Number number threadLocalValue.get();//每个线程计数加随机数Random r new Random();number.setNum(number.getNum() r.nextInt(100));//将其存储到ThreadLocal中threadLocalValue.set(number);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//打印保存的随机值System.out.println(Thread.currentThread().getName() -》 threadLocalValue.get().getNum());threadLocalValue.remove();System.out.println(Thread.currentThread().getName() remove方法之后 -》 threadLocalValue.get().getNum());}}public static void main(String[] args) {// 启动5个线程for (int i 0; i 5; i) {new ThreadDemo2().start();}}
}从示例2中可以看到每个线程可以通过 initialValue方法初始化变量值 。如果使用 public static ThreadLocalUseDemo2.Number number赋值会导致数值一样。因为是 static修饰的所有线程都指向同一个对象。
三、ThreadLocal源码分析
查看 ThreadLocal类的常用方法源码。
1、set()方法
查看 set方法。 public void set(T value) {// 1、获取当前线程Thread t Thread.currentThread();// 2、获取线程中的属性 threadLocalMap//如果threadLocalMap 不为空则直接更新要保存的变量值否则创建threadLocalMap并赋值ThreadLocalMap map getMap(t);if (map ! null)map.set(this, value);else// 初始化thradLocalMap 并赋值createMap(t, value);}1.1 ThreadLocalMap简介
ThreadLocalMap是 ThreadLocal的内部静态类其实 ThreadLocalMap是个标准的 Map实现内部有一个元素类型为 Entry 的数组用以存放线程可能需要的多个副本变量。 即 ThreadLocalMap的构成主要是用 Entry来保存数据 而且还是继承的弱引用。在 Entry内部使用 ThreadLocal作为key使用我们设置的value作为value。 查看 ThreadLocalMap类。 static class ThreadLocalMap {static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}private Entry getEntry(ThreadLocal? key) {int i key.threadLocalHashCode (table.length - 1);Entry e table[i];if (e ! null e.get() key)return e;elsereturn getEntryAfterMiss(key, i, e);}private void set(ThreadLocal? key, Object value) {// We dont use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.Entry[] tab table;int len tab.length;int i key.threadLocalHashCode (len-1);for (Entry e tab[i];e ! null;// hash冲突时使用了开放定址法线性探测再散列即依次向后查找。e tab[i nextIndex(i, len)]) {ThreadLocal? k e.get();if (k key) {e.value value;return;}if (k null) {replaceStaleEntry(key, value, i);return;}}tab[i] new Entry(key, value);int sz size;if (!cleanSomeSlots(i, sz) sz threshold)rehash();}private static int nextIndex(int i, int len) {return ((i 1 len) ? i 1 : 0);}// ...}可以看到 Entry 内部静态类它继承了 WeakReference弱引用一个key是 ThreadLocal?类型一个value是 Object 类型的值。 getEntry 方法是获取某个 ThreadLocal 对应的值。set 方法是更新或赋值相应的 ThreadLocal对应的值。
1.2 初始化ThreadLocalMap
接着查看 ThreadLocal.createMap方法。 void createMap(Thread t, T firstValue) {// 创建 ThreadLocalMapt.threadLocals new ThreadLocalMap(this, firstValue);}//ThreadLocalMap 构造方法ThreadLocalMap(ThreadLocal? firstKey, Object firstValue) {table new Entry[INITIAL_CAPACITY];int i firstKey.threadLocalHashCode (INITIAL_CAPACITY - 1);table[i] new Entry(firstKey, firstValue);size 1;setThreshold(INITIAL_CAPACITY);}注意t.threadLocals 的代表的字段信息。 通过这里我们发现每个线程都拥有一个 ThreadLocalMap类用 Entry存放保存数据在 Entry内部使用 ThreadLocal作为key使用我们设置的value作为value。从而保证每个线程内部的局部变量副本相互干扰。
2、get方法
查看 get方法。 public T get() {//1、获取当前线程Thread t Thread.currentThread();//2、获取当前线程的ThreadLocalMapThreadLocalMap map getMap(t);if (map ! null) {//3.1、获取threalLocalMap中存储的值ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {SuppressWarnings(unchecked)T result (T)e.value;return result;}}//3.2 如果是数据为null则初始化初始化的结果TheralLocalMap中存放key值为threadLocal值为nullreturn setInitialValue();}private T setInitialValue() {T value initialValue();Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null)map.set(this, value);elsecreateMap(t, value);return value;}get 方法其实就是拿到每个线程独有的 ThreadLocalMap。 然后再用 ThreadLocal 的当前实例拿到 Map 中的相应的 Entry然后就可以拿到相应的值返回出去。如果 Map 为空还会先进行 map 的创建初始化等工作并返回null。
3、remove方法
查看 remove方法。 public void remove() {ThreadLocalMap m getMap(Thread.currentThread());if (m ! null)m.remove(this);}/*** Remove the entry for key.*/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)]) {if (e.get() key) {e.clear();expungeStaleEntry(i);return;}}}remove方法直接将 ThrealLocal 对应的值从当前相差 Thread中的 ThreadLocalMap中删除。
如果使用完不删除的话就会涉及到内存泄露的问题。 这是因为 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用。 弱引用的特点是如果这个对象只存在弱引用那么在下一次垃圾回收的时候必然会被清理掉。 如果 ThreadLocal 没有被外部强引用的情况下在垃圾回收的时候会被清理掉的这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是value 是强引用不会被清理这样一来就会出现 key 为 null 的 value。因为一般定义ThreadLocal 变量通常被 private static修饰。
参考文章
ThreadLocal源码解析https://blog.csdn.net/qq_26470817/article/details/124993311 – 求知若饥虚心若愚。