果洛营销网站建设公司,济南网站定制制作,创新设计多功能水杯,名贵中药材初加工平台由浅入深全面解析ThreadLocal 目录 由浅入深全面解析ThreadLocal简介基本使用ThreadLocal与synchronized的区别ThreadLocal现在的设计#xff08;JDK1.8#xff09;ThreadLocal核心方法源码分析ThreadLocalMap源码分析弱引用与内存泄露#xff08;内存泄漏和弱引用没有直接关…由浅入深全面解析ThreadLocal 目录 由浅入深全面解析ThreadLocal简介基本使用ThreadLocal与synchronized的区别ThreadLocal现在的设计JDK1.8ThreadLocal核心方法源码分析ThreadLocalMap源码分析弱引用与内存泄露内存泄漏和弱引用没有直接关系ThreadLocal核心源码Hash冲突解决 简介
线程并发在多线程并发的场景下使用传递数据我们可以通过ThreadLocal在同一线程不同组件中传递公共变量线程隔离每个线程的变量都是独立的不会相互影响
基本使用 常用方法 代码案例实现 (1) 不使用ThreadLocal时模拟多线程存取数据
public class ThreadLocalDemo1 {private String content;public String getContent() {return content;}public void setContent(String content) {this.content content;}public static void main(String[] args) {ThreadLocalDemo1 threadLocalDemo new ThreadLocalDemo1();for (int i 0; i 5; i) {Thread thread new Thread(new Runnable() {Overridepublic void run() {/*** 每一个线程存一个变量过一会取出这个变量*/threadLocalDemo.setContent(Thread.currentThread().getName() 的数据);System.out.println(------------------------);System.out.println(Thread.currentThread().getName() ----- threadLocalDemo.getContent());}});thread.setName(线程 i);thread.start();}}
}结果
------------------------
线程0-----线程4的数据
------------------------
线程4-----线程4的数据
------------------------
线程2-----线程4的数据
------------------------
线程3-----线程4的数据
------------------------
线程1-----线程4的数据2 使用ThreadLocal对多线程进行数据隔离把数据绑定到ThreadLocal 传统解决方案首先想到的就是加锁确实可以实现但是却牺牲了效率需要等待上一个线程之行结束才可以往下之行
public class ThreadLocalDemo2 {ThreadLocalString threadLocal new ThreadLocal();private String content;public String getContent() {return threadLocal.get();}public void setContent(String content) {threadLocal.set(content);}public static void main(String[] args) {ThreadLocalDemo2 threadLocalDemo2 new ThreadLocalDemo2();for (int i 0; i 5; i) {Thread thread new Thread(new Runnable() {Overridepublic void run() {/*** 每一个线程存一个变量过一会取出这个变量*/threadLocalDemo2.setContent(Thread.currentThread().getName()的数据);System.out.println(------------------------);System.out.println(Thread.currentThread().getName() ----- threadLocalDemo2.getContent());}});thread.setName(线程 i);thread.start();}}
}结果
------------------------
------------------------
------------------------
线程3-----线程3的数据
------------------------
线程2-----线程2的数据
线程1-----线程1的数据
线程0-----线程0的数据
------------------------
线程4-----线程4的数据ThreadLocal与synchronized的区别
二者都是用来处理多线程并发访问的问题但是二者的原理和侧重点不一样简要说就是ThreadLocal牺牲了空间而synchronized是牺牲了时间来保证线程安全隔离。 总结在上述的案例当中使用ThreadLocal更为合理这样保证了程序拥有了更高的并发性。
ThreadLocal现在的设计JDK1.8
简介 每一个Thread维护一个ThreadLocalMap这个Map的key为ThreadLocal实例本身而value则为实际存储的值。具体过程 1每一个Thread内部都有一个MapThreadLocalMap 2Map里面存储的ThreadLocal对象key和线程的变量副本value 3Thread的Map是由ThreadLocal来维护的由ThreadLocal负责向Map获取和设置线程的变量值。 4对于线程获取值每一个副本只能获取当前线程本地的副本值别的线程无法访问到互不干扰实现了线程隔离。对比与1.8之前的设计相当于Thread与ThreadLocal的角色互换了 1.8设计的好处 1每个Map存储的Entry数量变少了因为实际状况下Thread比ThreadLocal多 2当Thread销毁时ThreadLocalMap也会随之销毁避免内存的浪费
ThreadLocal核心方法源码分析 get方法源码
public T get() {// 获取当前线程Thread t Thread.currentThread();// 获取ThreadLocalMapThreadLocalMap map getMap(t);// map不为空时获取里面的Entryif (map ! null) {ThreadLocalMap.Entry e map.getEntry(this);if (e ! null) {SuppressWarnings(unchecked)T result (T)e.value;// 返回结果return result;}}// 没有则赋值初始值null并返回return setInitialValue();}set方法源码
public void set(T value) {// 获取当前线程Thread t Thread.currentThread();// 获取ThreadLocalMapThreadLocalMap map getMap(t);if (map ! null) {// 不为空直接setmap.set(this, value);} else {// map为空则创建并setcreateMap(t, value);}}initialValue方法返回初始值protected修饰为了让子类覆盖设计的需要自定义初始值可以重写该方法
protected T initialValue() {return null;}remove方法
public void remove() {// 获取当前线程的ThreadLocalMapThreadLocalMap m getMap(Thread.currentThread());if (m ! null) {// 移除ThreadLocalMapm.remove(this);}}setInitialValue方法
private T setInitialValue() {// 得到初始化值nullT value initialValue();// 获取当前线程Thread t Thread.currentThread();// 获取线程中ThreadLocalMapThreadLocalMap map getMap(t);// map存在的话把null设置进去不存在则创建一个并将null设置进去if (map ! null) {map.set(this, value);} else {createMap(t, value);}// 如果当前ThreadLocal属于TerminatingThreadLocal关闭的ThreadLocal则register注册到TerminatingThreadLocalif (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal?) this);}return value;}ThreadLocalMap源码分析
简介 ThreadLocalMap是ThreadLocal的内部类没有实现Map接口是独自设计实现Map功能内部的Entry也是独立的。结构图解 成员变量 // Entry类继承弱应用为了和Thread的生命周期解绑static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}/*** The initial capacity -- MUST be a power of two.* 初始容量必须是二的幂*/private static final int INITIAL_CAPACITY 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.* 根据需要调整大小。长度必须是2的幂。*/private Entry[] table;/*** The number of entries in the table.* table中的entrie数量*/private int size 0;/*** The next size value at which to resize.* 要调整大小的下一个大小值*/private int threshold; // Default to 0/*** Set the resize threshold to maintain at worst a 2/3 load factor.* 设置调整大小阈值以维持最坏的2/3负载因子*/private void setThreshold(int len) {threshold len * 2 / 3;}/*** 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);}弱引用与内存泄露内存泄漏和弱引用没有直接关系
内存泄漏/溢出概念 1Memory overflow内存溢出没有足够的空间提供给申请者使用 2Memory leak内存泄漏系统中已动态分配的堆内存由于某种原因无法释放或者没有释放导致系统内存堆积影响系统运行甚至导致系统崩溃。内存泄漏终将导致内存溢出。强/弱引用概念 1Strong Referce强引用我们常见的对象引用只要有一个强引用指向对象也就表明还“活着”这种状况下垃圾回收机制GC是不会回收的。 2Weak Referce弱引用继承了WeakReferce的对象垃圾回收器发现了只具有弱引用的对象不管当前系统的内存是否充足都会回收他的内存。如果key即Entry使用强引用也无法避免内存泄漏 因为Entry是在Thread当前线程中生命周期和Thread一样没有手动删除Entry时Entry就会内存泄漏。也就是说只要在调用完ThreadLocal后及时使用remove方法才能避免内存泄漏
ThreadLocal核心源码Hash冲突解决
从构造方法入手
ThreadLocalMap(ThreadLocal? firstKey, Object firstValue) {// 初始化tabletable new Entry[INITIAL_CAPACITY];// 计算索引在数组中的位置核心代码int i firstKey.threadLocalHashCode (INITIAL_CAPACITY - 1);// 设置值table[i] new Entry(firstKey, firstValue);size 1;// 设置阈值INITIAL_CAPACITY的三分之二setThreshold(INITIAL_CAPACITY);}重点分析int i firstKey.threadLocalHashCode (INITIAL_CAPACITY - 1);
private final int threadLocalHashCode nextHashCode();private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}private static AtomicInteger nextHashCode new AtomicInteger();private static final int HASH_INCREMENT 0x61c88647;public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);}1这里定义了一个AtomicInteger每次获取并加上HASH_INCREMENT0x61c88647这个值与斐波那契数黄金分割有关是为了让哈希码能够均匀的分布在2的n次方的数组Entry[]里面也就尽可能避免了哈希冲突。 2hashcode (INITIAL_CAPACITY - 1) 相当于hashcode % (INITIAL_CAPACITY - 1) 的高效写法所以size必须为2的次幂这样最大程度避免了哈希冲突。
set方法源码分析
private void set(ThreadLocal? key, Object value) {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)]) {// 获取到该Entry对应的ThreadLocalThreadLocal? k e.get();if (k key) {// key存在则覆盖valuee.value value;return;}// key为null但是值不为null这说明了之前使用过但是ThreadLocal被垃圾回收了当前的Entry是一个陈旧的Stale元素if (k null) {// keyThreadLocal不存在则新Entry替换旧的Entry此方法做了不少垃圾清理的动作避免了内存泄漏。replaceStaleEntry(key, value, i);return;}}// ThreadLocal中未找到key也没有陈旧的元素此时则在这个位置新创建一个Entrytab[i] new Entry(key, value);int sz size;// cleanSomeSlots用于清理e.get()为null的key如果大于阈值2/3容量则rehash执行一次全表扫描清理工作if (!cleanSomeSlots(i, sz) sz threshold)rehash();}/*** 线性探测法查找元素到最后一个时重定位到第一个*/private static int nextIndex(int i, int len) {return ((i 1 len) ? i 1 : 0);}详细视频可前往B站黑马程序员