寻乌建设局网站,帝国cms网站源码,企业网站程序下载,江门医疗网站建设文章目录一、Thread简介1.什么是ThreadLocal2.为什么要是用ThreadLocal2.1Synchronized、Lock保证线程安全2.2ThreadLocal保证线程安全3.ThreadLocal和Synchronized的区别二、ThreadLocal原理1.Thread抽象内部结构2.ThreadLocal源码2.1Thread、ThreadLocal、ThreadLocalMap、En…
文章目录一、Thread简介1.什么是ThreadLocal2.为什么要是用ThreadLocal2.1Synchronized、Lock保证线程安全2.2ThreadLocal保证线程安全3.ThreadLocal和Synchronized的区别二、ThreadLocal原理1.Thread抽象内部结构2.ThreadLocal源码2.1Thread、ThreadLocal、ThreadLocalMap、Entry之间关系2.2ThreadLocal类的set()方法2.3ThreadLocal类的get()方法2.4ThreadLocal类的remove()方法2.5面试说一说ThreadLocal原理、Thread如何实现线程隔离的三、深究ThreadLocal1.为什么不直接用线程id作为ThreadLocalMap的key呢2.ThreadLocal导致内存泄漏的原因3.ThreadLocalMap在设计中有没有考虑到内存泄漏这点呢4.在使用ThreadLocal中我们应该注意什么5.key是弱引用GC回收会影响ThreadLocal的正常工作嘛6.Entry的key为什么要设计成弱引用呢为什么不使用强引用6.1Entry的key如果使用强引用6.2为什么要设计成弱引用6.3四种引用类型7.我们希望父子线程之间共享数据应该怎么做呢7.1InheritableThreadLocal7.2InheritableThreadLocal是如何实现父子线程之间共享的一、Thread简介
1.什么是ThreadLocal
ThreadLocal即线程本地变量。如果你创建了一个ThreadLocal变量那么访问这个变量的每个线程都会有这个变量的一个本地拷贝多个线程操作这个变量的时候实际是在操作自己本地内存里面的变量从而起到线程隔离的作用保证了线程安全。
因为每个 Thread 内有自己的实例副本且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。每个 Thread 有自己的实例副本且其它 Thread 不可访问那就不存在多线程间共享的问题。 2.为什么要是用ThreadLocal
保证线程安全并发场景下会出现多个线程共享一个变量的场景这种场景可能会出现线程安全性问题我们可以采取加锁的方式(Synchronized、Lock)方式也可以使用使用ThreadLocal方式来避免线程安全问题。
2.1Synchronized、Lock保证线程安全
采用加锁方式保证线程安全到导致系统变慢。共享变量某个时刻只能由一个线程访问其他线程需要等到该线程释放锁才能访问影响系统性能。
2.2ThreadLocal保证线程安全
使用ThreadLocal。使用ThreadLocal类访问共享变量时会在每个线程的本地都保存一份共享变量的拷贝副本。多线程对共享变量修改时实际上操作的是这个变量副本从而保证线性安全。 3.ThreadLocal和Synchronized的区别
ThreadLocal和Synchronized都是为了解决并发问题。Synchronized用于线程共享 而ThreadLocal用于线程隔离。Synchronized是时间换空间ThreadLocal是空间换时间。Synchronized是利用锁的机制使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本使得每个线程在某一时间访问到的并不是同一个对象这样就隔离了多个线程对数据的数据共享。
二、ThreadLocal原理
1.Thread抽象内部结构
简单理解看不懂先往下看再回顾
Thread类中有ThreadLocal.ThreadLocalMap threadLocals属性ThreadLocalMap 是Thread的静态内部类它维护着Entry对象数组每个Entry代表一个ThreadLocalMap对象Entry是ThreadLocalMap的静态内部类存储采用的是k-v形式key为ThreadLocalvalue为我 2.ThreadLocal源码
2.1Thread、ThreadLocal、ThreadLocalMap、Entry之间关系
先根据源码砍砍他们之间的关系
Thread的成员变量ThreadLocal.ThreadLocalMapThreadLocal.ThreadLocalMap是ThreadLocal的静态内部类Entry是ThreadLocalMap的静态内部类Entry继承了弱引用也就是说如果外部没有强引用关联的话下一次GC时会被回收。在Entry是一个k-v形式内部使用ThreadLocal作为key使用我们设置的value作为value。Entry[] table为ThreadLocal的属性由ThreadLocalMap维护
//Thread类
class Thread implements Runnable {//ThreadLocalMap是Thread的成员变量ThreadLocal.ThreadLocalMap threadLocals null;//ThreadLocal类
public class ThreadLocalT {//Entry数组是ThreadLocal的成员变量该数组由ThreadLocalMap维护private Entry[] table;//静态内部类ThreadLocalMap
static class ThreadLocalMap {//Entry是ThreadLocalMap的静态内部类注意Entry继承了弱引用如果没有被强引用关联下一次GC会被回收static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}
}
}
2.2ThreadLocal类的set()方法
通过源码可以看到ThreadLocalMap维护了Entry数组Entry是k-v形式key为ThreadLocalvalue为我们传入的值当同一个线程对同一个ThreadLocal进行两次set时value会被覆盖。 获取当前线程根据当前线程获取ThreadLocalMap判断ThreadLocalMap是否存在存在则将ThreadLocal作为key传入的值为value存入ThreadLocalMap的Entry中不存在则根据ThreadLocalMap构造函数创建ThreadLocalMap并将ThreadLocal作为key传入的值为value存入ThreadLocalMap的Entry中 public void set(T value) {Thread t Thread.currentThread();//获取当前线程tThreadLocalMap map getMap(t);//根据当前线程获取到ThreadLocalMapif (map ! null)map.set(this, value);//如果map不为空则将当前对象ThreadLocal作为key传入的值为value存入ThreadLocalMap中elsecreateMap(t, value);//如果map为空则创建ThreadLocalMap对象后再将k-v存入ThreadLocalMap中}//该方法位于ThreadLocal中ThreadLocalMap getMap(Thread t) {return t.threadLocals;//根据线程t获取Thrad的ThreadLocalMap}//该方法位于ThreadLocal中void createMap(Thread t, T firstValue) {t.threadLocals new ThreadLocalMap(this, firstValue);//调用ThreadLocalMap构造函数this表示当前类ThreadLocal}//该方法位于ThreadLocal中该方法位于ThreadLocal中构造函数维护这Entry数组tableThreadLocalMap(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);} 2.3ThreadLocal类的get()方法 获取当前线程t根据线程t获取ThradLocalMap mapmap存在则获取EntryEntry存在获取valuemap不存在初始化ThradLocalMap并将ThreadLocal作为kayvaleu为null存进ThreadLocalMap中返回value也就是null。调用get()方法时ThreadLocalMap没有初始化则会初始化并ThreadLocal作为kayvaleu为null存进ThreadLocalMap中 public T get() {Thread t Thread.currentThread();//获取当前线程ThreadLocalMap map getMap(t);if (map ! null) {ThreadLocalMap.Entry e map.getEntry(this);//map不为空获取Entryif (e ! null) {//entry不为空。返回valueSuppressWarnings(unchecked)T result (T)e.value;return result;}}return setInitialValue();//map为空初始化threadLocals成员变量的值也就是初始化把thread Local为keyvaluenull塞进entry}private T setInitialValue() {T value initialValue(); //初始化value的值Thread t Thread.currentThread(); ThreadLocalMap map getMap(t); //以当前线程为key获取threadLocals成员变量它是一个ThreadLocalMapif (map ! null)map.set(this, value); //KV设置到ThreadLocalMap中elsecreateMap(t, value); //实例化threadLocals成员变量return value;//返回null}protected T initialValue() {return null;} 2.4ThreadLocal类的remove()方法 获取当前线程根据当前线程获取ThreadLocalMap如果map部位空则删除map中指定的的Entry public void remove() {ThreadLocalMap m getMap(Thread.currentThread());//获取当前线程的ThreadLocalMap变量if (m ! null)m.remove(this);//对象不为空则删除
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
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;}}
} 2.5面试说一说ThreadLocal原理、Thread如何实现线程隔离的
ThreadLocal叫本地线程变量作用就是当多线程访问共享变量时起到线程隔离的作用每个线程都有自己的一个副本且之间不被共享这种方式采用的是空间换时间的方式。目的是保证线程安全采用空间换时间的方式保证线程安全。
每个线程Thread都有一个成员变量ThreadLocal.ThreadLocalMap thradLocals。也就是说每个线程都有一个ThreadLocalMap这个ThreadLocalMap是ThreadLocal的静态内部类他维护者Entry对象数组Entry对象存储方式是k-v的形式。k为ThreadLocalV为我我们set进去的值。
在并发场景下每个线程在往ThreadLocal里面设置值得时候实际就是存进自己的thradLocals属性中以ThreadLocal为keyset进去的值为value。实现线程隔离。 三、深究ThreadLocal
1.为什么不直接用线程id作为ThreadLocalMap的key呢 ThreadLocalMap是Thread的属性维护着Entry数组Entry的key是ThradLocalvalue为我们set入的值。 如果将线程id作为key那么当一个Thread有多个ThreadLocal进行set()的时候无法区分value是哪个ThreadLocal的或者说无论多少个ThreadLocal每次set进去由于key都是ThreadId会导致每次set都会被覆盖。 如图所示一个key对应多个ThreadLocal。ThreadLocal-1 set()的时候key为ThreadThreadLocal-1 set()的时候key依然为Thread 2.ThreadLocal导致内存泄漏的原因 ThreadLocal导致内存泄漏愿意你有两个 使用完后没有remove() 由于ThreadLocalMap 的生命周期跟 Thread 一样长对于重复利用的线程来说例如核心线程池中的线程如果没有手动删除调用remove()方法会导致Entry对象越来越多从而导致内存泄漏第二种原因下面讲解 Entry类继承了弱引用super(k);使ThreadLocal也是一个弱引用
static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}
}ThreadLocal引用示意图 ThreadLocalMap使用ThreadLocal的弱引用作为key当ThreadLocal变量被手动设置为null即一个ThreadLocal没有外部强引用来引用它当系统GC时ThreadLocal一定会被回收。这样的话ThreadLocalMap中就会出现key为null的Entry就没有办法访问这些key为null的Entry的value如果当前线程再迟迟不结束的话(比如线程池的核心线程)这些key为null的Entry的value就会一直存在一条强引用链Thread变量 - Thread对象 - ThreaLocalMap - Entry - value - Object 永远无法回收造成内存泄漏。
当Thread手动设置为null后的因用链 此时堆中的ThreadLocal只存在弱引用再下一次GC时会被回收。回收后Entry中的keynull这个Entry就没有办法被访问。导致内存泄漏。 3.ThreadLocalMap在设计中有没有考虑到内存泄漏这点呢
实际上ThreadLocalMap在设计的时候就考虑到这个情况了所以ThreadLocal的get、set方法中加了一些防护措施。在执行set、get方法的时候会清楚线程中ThreadLocalMap中key为null的Entry。
ThreadLocal的set()方法防止内存泄漏措施 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;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;//触发一次Log2(N)复杂度的扫描,目的是清除过期Entry if (!cleanSomeSlots(i, sz) sz threshold)rehash();}ThreadLocal的get()方法防止内存泄露措施
public T get() {Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null) {//去ThreadLocalMap获取Entry方法里面有keynull的清除逻辑ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {SuppressWarnings(unchecked)T result (T)e.value;return result;}}return setInitialValue();}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//其中有key为null的清楚逻辑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)// Entry的key为null,则表明没有外部引用,且被GC回收,是一个过期EntryexpungeStaleEntry(i);elsei nextIndex(i, len);e tab[i];}return null;}4.在使用ThreadLocal中我们应该注意什么
将ThreadLocal变量定义成private static的这样的话ThreadLocal的生命周期就更长由于一直存在ThreadLocal的强引用所以ThreadLocal也就不会被回收也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值然后remove它防止内存泄露每次使用完ThreadLocal都调用它的remove()方法清除数据。 5.key是弱引用GC回收会影响ThreadLocal的正常工作嘛 弱引用:具有弱引用的对象拥有更短暂的生命周期。如果一个对象只有弱引用存在了则下次GC将会回收掉该对象不管当前内存空间足够与否 当然不会因为还有ThreadLocal强引用着它是不会被GC回收的除非手动将ThradLocal置为null 验证
package com.jhq.threadLocal;import java.lang.ref.WeakReference;
/*** BelongsProject: study* BelongsPackage: com.jhq.threadLocal* Author: jianghq* CreateTime: 2023-02-24 17:02* Description: ThreadLocal的key既然是弱引用.会不会GC贸然把key回收掉进而影响ThreadLocal的正常使用 不会* Version: 1.0*/
public class WeakReferenceTest {public static void main(String[] args) {Object object new Object();WeakReferenceObject testWeakReference new WeakReference(object);System.out.println(GC回收之前弱引用testWeakReference.get());//触发系统垃圾回收for(int i0;i100;i){//我们只能建议GC回收并不能百分之百保证真的回收System.gc();}System.out.println(GC回收之后弱引用testWeakReference.get());//手动设置为object对象为nullobjectnull;System.gc();System.out.println(对象object设置为nullGC回收之后弱引用testWeakReference.get());}
}
输出GC回收之前弱引用java.lang.Objectcc34f4dGC回收之后弱引用java.lang.Objectcc34f4d对象object设置为nullGC回收之后弱引用null 6.Entry的key为什么要设计成弱引用呢为什么不使用强引用
官方回答 o help deal with very large andlong-lived usages, the hash table entries use WeakReferences for keys. 为了应对非常大和长时间的用途哈希表使用弱引用的 key。 6.1Entry的key如果使用强引用
先看看引用图 正这种情况由于ThreadLocalMap生命周期和Thread一样长使用强引用之后只要Thrad存在那么Entry就会一直存在内存中如果线程为核心线程池中的线程Entry就会一直存在导致内存泄漏。
6.2为什么要设计成弱引用
如果Key使用弱引用ThreadLocal置为null因为ThreadLocalMap持有ThreadLocal的弱引用即使没有手动删除ThreadLocal也会被回收。value则在下一次ThreadLocalMap调用set,getremove的时候会被清除。
6.3四种引用类型
强引用我们new出来的对象就是强引用例如Object onew Onject();强引用不会被GC回收。软引用一个对象只有软引用时内存空间足够的情况下不会被回收当内存不足时会被回收。弱引用当对象只有弱引用时下一次GC时会被回收。虚引用如果一个对象仅持有虚引用那么它就和没有任何引用一样在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。 7.我们希望父子线程之间共享数据应该怎么做呢
7.1InheritableThreadLocal
使用InheritableThreadLocal
public class InheritableThreadLocalTest {public static void main(String[] args) {ThreadLocal threadLocalnew ThreadLocal();InheritableThreadLocal inheritableThreadLocalnew InheritableThreadLocal();threadLocal.set(main线程.ThreadLocal);inheritableThreadLocal.set(main线程.InheritableThreadLocal);new Thread(()-{threadLocal.set(Thread.currentThread().getName()ThreadLocal);inheritableThreadLocal.set(Thread.currentThread().getName()InheritableThreadLocal);}).start();new Thread(()-{System.out.println(当前线程Thread.currentThread().getName()threadLocal.get());System.out.println(当前线程Thread.currentThread().getName()inheritableThreadLocal.get());}).start();}
}//输出
当前线程Thread-0null
当前线程Thread-0main线程.InheritableThreadLocal
可以看出ThreadLocal在父子线程之间是不共享的。InheritableThreadLocal可以在父子线程之间共享。但仅限于父子线程之间。
7.2InheritableThreadLocal是如何实现父子线程之间共享的
InheritableThreadLocal是Thread的成员变量返回类型也是ThreadLocal.ThreadLocalMap /* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals null;Thread的init方法 private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {//.....衡略//如果父线程的inheritableThreadLocals不为空则将inheritableThreadLocals赋值给子类if (parent.inheritableThreadLocals ! null)this.inheritableThreadLocals ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize stackSize;/* Set thread ID */tid nextThreadID();}static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);}