什么是ThreadLocal,它的基本用法是什么
简单来说就是能在多线程中保持变量独立的线程对象
不用Threadlocal多线程访问同一个变量会出现的问题
package com.pxx;
/**
* Created by Administrator on 2023/9/3.
*/
public class Demo1 {
private String v1;
public String getV1() {
return v1;
}
public void setV1(String v1) {
this.v1 = v1;
}
public static void main(String[] args) {
//开启一个多线程去设置并且访问这个变量
Demo1 d1 = new Demo1();
//这里会循环五个线程
for(int i = 0;i < 5;i++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//设置并且打印一个变量的数据
d1.setV1("data:" + Thread.currentThread().getName());
System.out.println("-------------");
//取出这个线程对应的名字和值
System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
}
});
//设置一下每一个线程的名字
t1.setName("线程" + i);
t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
}
}
}
下面直接已经线程混乱
一般来说我们可以用锁来解决,比如引入synchronized,这里我们先不用锁,我们用ThreadLocal这个类去解决
ThreadLocal类去解决线程不同步的问题
它的目的是保持变量独立
下面我们去看一下常见的方法
set():将变量绑定到当前线程中
get():获取当前线程绑定的变量
修改一下上面的代码
package com.pxx;
/**
* Created by Administrator on 2023/9/3.
*/
public class Demo1 {
//引入绑定变量的线程对象
ThreadLocal tl1= new ThreadLocal();
private String v1;
public String getV1() {
// return v1;
return tl1.get();//得到通过set绑定的变量
}
public void setV1(String v1) {
//this.v1 = v1;
tl1.set(v1);//直接把这个v1绑定到对象里面
}
public static void main(String[] args) {
Demo1 d1 = new Demo1();
//这里会循环五个线程
for(int i = 0;i < 5;i++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//设置并且打印一个变量的数据
d1.setV1("data:" + Thread.currentThread().getName());
System.out.println("-------------");
//取出这个线程对应的名字和值
System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
}
});
//设置一下每一个线程的名字
t1.setName("线程" + i);
t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
}
}
}
运行结果:
ThreadLocal与synchronized的区别
先把上面的代码变成synchronized给锁住
package com.pxx;
/**
* Created by Administrator on 2023/9/3.
*/
public class Demo3 {
//引入绑定变量的线程对象
ThreadLocal tl1 = new ThreadLocal();
private String v1;
public String getV1() {
return v1;
}
public void setV1(String v1) {
this.v1 = v1;
}
public static void main(String[] args) {
Demo3 d1 = new Demo3();
//这里会循环五个线程
for(int i = 0;i < 5;i++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (d1) {//这个加锁了
//设置并且打印一个变量的数据
d1.setV1("data:" + Thread.currentThread().getName());
System.out.println("-------------");
//取出这个线程对应的名字和值
System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
}
}
});
//设置一下每一个线程的名字
t1.setName("线程" + i);
t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
}
}
}
运行结果:
很明显是锁住了
先来说一下两者的共同点:都是处理多线程并发访问变量的问题
synchronized:它的效率会低一点,因为相当于就是说,线程是排队进行访问的,就像一个教室只有一个厕所,大家都要进去上,就要排队
ThreadLocal:效率高,相当于线程可以同时并发访问,效率高,就像一个教室多个厕所,彼此上,但是相互独立
ThreadLocal的内部结构
最早的一个设计原理
关注一下JDK8,它是把Thread每一个线程作为了一个主线,然后Entry里面存放的是 ThreadLocal这样一个线程对象
这样的设计方案有两个好处:
1.每个map存储的entry变少,因为就是怎么说,Thread线程肯定比ThreadLocal这样一个线程多
2.那么主线Thread销毁掉,里面的map数据对象也就被销毁了
我分析一下源码
先去看set方法
再去看一下ThreadLocalMap这个类
set就是添加到了这个map对象里面
这样不就说明一个问题:解决了线程并发访问,变量出错的问题
相当是什么,自己去上自己的厕所,彼此之间相互独立不影响
可能会造成的一个内存泄漏的问题
他分为两种情况来看:
第一种: 内存不够了,只有溢出memory overflow
第二种:内存泄漏memory leak,在堆上的空间得不到释放,会造成浪费,影响了程序的运行速度,这种浪费多了,就会造成内存的溢出
我们这里再来引入两个概念
第一个:什么是强引用?我们正常的引用一个对象,没有指向的时候,就会被gc掉,也就是垃圾回收器给回收掉
第二个:什么是弱引用?一句来说,遇到gc就会被回收。只要垃圾回收机制一运行,不管jvm的内存空间时候是否足够,都会回收该对象占用的内存
下面用一张图展示一下引用关系
假如是key是强引用的情况
假设ThreadLocal用完了,引用被收回,又因为Map里面的ThreadLocal是一个强引用,所以ThreadLocal对象无法被回收掉
在没手动remove掉Entry类以及CurrentThread依然运行的情况下,Entry类根本不会被挥挥手,会造成内存泄漏
假设key是弱引用的情况下,ThreadLocal引用没了,map 里面是一个弱引用指向ThreadLocal,那么就表明ThreadLocal会马上被垃圾回收器给回收掉,他一回收掉,Key就为NULL,那么我们就再也无法访问到value ,value无法被回收,会导致内存泄漏
很明显源代码给出的是一个弱引用
上面也说明了强引用还是弱引用都会造成内存泄漏
那么造成内存泄漏的根本原因就是:
第一点:Entry类始终存在内存中,没有手动remove
第二点:CurrentThread线程依然在运行
好了,祝你早安午安晚安。