一.ThreadLocal的初始化方法在什么情况下不会执行?
当ThreadLocal中出现set方法之后,所有类型的初始化方法就不会执行了
原因:ThreadLocal在执行get方法的时候,才会判断(有没有set方法)并调用初始化方法(懒加载)
public class ThreadLocalDemo4 {
//创建并初始化ThreadLocal
private static ThreadLocal<String> threadLocal=
ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
System.out.println("执行了withInitial方法");
return Thread.currentThread().getName()+"java";
}
});
public static void main(String[] args) {
Runnable runnable=new Runnable() {
@Override
public void run() {
threadLocal.set("MySQL");
//获取ThreadLocal
String result=threadLocal.get();
System.out.println(Thread.currentThread().getName()+" 获取到的内容:"+result);
}
};
Thread t1=new Thread(runnable,"线程1");
t1.start();
Thread t2=new Thread(runnable,"线程2");
t2.start();
}
}

二.ThreadLocal归属关系(看源码)
value是你要存储的值
面试题:一个线程只会使用最后保存的值,为什么要用map来存储?
因为ThreadLocalMap存储的是所有线程使用的值
三.ThreadLocal使用场景
1.解决线程安全问题
2.实现线程级别的数据传递(在公共的地方set值,用的时候在ThreadLocal里直接get)
四.ThreadLocal缺点
1.不可继承性
private static ThreadLocal<String> threadLocal=new ThreadLocal();
public static void main(String[] args) {
threadLocal.set("java");
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
}
});
t1.start();
}

不可继承性解决方案:InheritableThreadLocal(在主线程中设置值,在子线程中可以得到值)
InheritableThreadLocal(可继承的ThreadLocal)是ThreadLocal的子类
private static ThreadLocal threadLocal=new InheritableThreadLocal();
public static void main(String[] args) {
threadLocal.set("java");
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
}
});
t1.start();
}
![]()
注意事项:即使使用了InheritableThreadLocal,也不能实现并列线程(没有继承关系)之间的数据传输(数据设置和获取)
2.脏读(在一个线程中读取到了不属于自己的信息就叫做脏读)
脏读产生原因:线程池复用了线程,和这个线程相关的静态属性也复用,所以就导致了脏读
//脏读问题
private static ThreadLocal<String> threadLocal=new ThreadLocal<>();
public static void main(String[] args) {
ThreadPoolExecutor executor=new ThreadPoolExecutor(1,1,0,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(1000));
for (int i=0;i<2;i++){
executor.submit(new MyThreadLocal());
}
}
static class MyThreadLocal extends Thread{
//静态变量被复用
private static boolean flag=false;
@Override
public void run() {
String tname=this.getName();
if(!flag){
//第一次执行
threadLocal.set(tname);
System.out.println(tname+"设置了"+tname);
flag=true;
}
System.out.println(tname+"得到了"+threadLocal.get());
}
}

解决脏读问题:a.避免使用静态变量 b.使用完之后,执行remove移除
3.内存溢出(配合线程池使用的时候才会出现)
当一个线程使用完资源之后,没有释放资源,或者说释放资源不及时就是内存溢出
//设置最大内存的是5M
public class ThreadLocalDemo11 {
private static ThreadLocal<MyThreadLocal>threadLocal=new ThreadLocal<>();
public static void main(String[] args) {
ThreadPoolExecutor executor=new ThreadPoolExecutor(10,10,0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
for(int i=0;i<10;i++){
executor.execute(new Runnable() {
@Override
public void run() {
MyThreadLocal myThreadLocal=new MyThreadLocal();
threadLocal.set(myThreadLocal);
System.out.println(Thread.currentThread().getName()+
"设置了值");
}
});
}
}
static class MyThreadLocal{
private byte[]bytes=new byte[1*1024*1024];
}
}

解决内存溢出:使用remove移除
内存溢出原因(配合线程池使用的时候才会出现内存溢出):
1.线程池是长生命周期,使用完后不会主动释放资源
2.Thread–>ThreadLocalMap–>Entry[ ] key,value(1MB资源) key是弱引用,value是强引用,垃圾回收器不会回收value资源
垃圾回收可回收的资源相关的4种Java引用类型
1.强引用Object obj=new Object(); 即使发生OOM也不会进行垃圾回收
2.软引用,它的引用关系仅次于强引用。如果内存够用,那么垃圾回收器不会考虑回收此引用,将要发生OOM的时候才会回收此引用
3.弱引用,不管内存够不够用,下一次回收都会将此引用的对象回收掉
4.虚引用:创建即回收,它可以触发一个垃圾回收的回调
ThreadLocal关系结构图

面试题:为什么ThreadLocal会将key值设置为弱引用?
答:ThreadLocal为了更大程度的避免OOM
面试题:ThreadLocal的原理?
ThreadLocal是线程的本地变量,它是属于线程的,在线程里有一个Map,Map里有一个Entry,Entry里有key和value,key就是ThreadLocal,value就是要存储的值,通过这种方式把所有的线程的本地变量放在一个map里面。在调用get方法时,先根据线程得到map,再在map里面取到相应的值。
五.解决哈希冲突
两者哈希冲突的解决方案是不同的
HashMap:链表法+红黑树
链表升级为红黑树的条件:a.链表长度大于8; b.数组长度大于64 ;
红黑树降级为链表:链表长度变为6之后
ThreadLocalMap:开放寻址法(数据量小的使用场景)
为什么要从链表升级为红黑树?
加速查询性能(链表O(n) ,红黑树O(log n) )