ThreadLocal 使用 以及源码分析
ThreadLocal简介
①. ThreadLocal是什么
首先理解JMM模型
①. ThreadLocal本地线程变量,线程自带的变量副本(实现了每一个线程副本都有一个专属的本地变量,主要解决的就是让每一个线程自己用自己的,不跟别人争抢。通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全的问题
)
②. synchronized或者lock,有个管理员,好比,现在大家签到,多个同学(线程),但是只有一只笔,只能同一个时间,只有一个线程(同学)签到,加锁(同步机制是以时间换空间,执行时间不一样,类似于排队)
③. ThreadLocal,人人有份,每个同学手上都有一支笔,自己用自己的,不用再加锁来维持秩序(同步机制是以空间换时间,为每一个线程都提供了一份变量的副本,从而实现同时访问,互不干扰同时访问,肯定效率高啊)
jdk文档中翻译
api方法
使用
需求1: 5个销售卖房子,集团高层只关心销售总量的准确统计数
package com.bilibili.juc.tl;
import java.util.Random;
import java.util.concurrent.TimeUnit;
class House //资源类
{
int saleCount = 0;
public synchronized void saleHouse()
{
++saleCount;
}
}
/**
* @auther zzyy
* @create 2021-12-31 15:46
*
* 需求1: 5个销售卖房子,集团高层只关心销售总量的准确统计数。
*
*
*
*
*/
public class ThreadLocalDemo
{
public static void main(String[] args) throws InterruptedException
{
House house = new House();
for (int i = 1; i <=5; i++) {
new Thread(() -> {
int size = new Random().nextInt(5)+1;
System.out.println(size);
for (int j = 1; j <=size; j++) {
house.saleHouse();
}
},String.valueOf(i)).start();
};
//暂停毫秒
try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套: "+house.saleCount);
}
}
需求2: 5个销售卖完随机数房子,各自独立销售额度,自己业绩按提成走,分灶吃饭,各个销售自己动手,丰衣足食
使用 ThreadLocal
package com.bilibili.juc.tl;
import lombok.Getter;
//import sun.font.FontRunIterator;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
class House //资源类
{
int saleCount = 0;
public synchronized void saleHouse()
{
++saleCount;
}
ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);
public void saleVolumeByThreadLocal()
{
saleVolume.set(1+saleVolume.get());
}
}
/**
* @auther zzyy
* @create 2021-12-31 15:46
*
*
*
* 需求2: 5个销售卖完随机数房子,各自独立销售额度,自己业绩按提成走,分灶吃饭,各个销售自己动手,丰衣足食
*
*
*/
public class ThreadLocalDemo
{
public static void main(String[] args) throws InterruptedException
{
House house = new House();
for (int i = 1; i <=5; i++) {
new Thread(() -> {
int size = new Random().nextInt(5)+1;
try {
for (int j = 1; j <=size; j++) {
house.saleHouse();
house.saleVolumeByThreadLocal();
}
System.out.println(Thread.currentThread().getName()+"\t"+"号销售卖出:"+house.saleVolume.get());
} finally {
house.saleVolume.remove();
}
},String.valueOf(i)).start();
};
//暂停毫秒
try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName()+"\t"+"共计卖出多少套: "+house.saleCount);
}
}
阿里巴巴开发手册提到
package com.bilibili.juc.tl;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class MyData
{
ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
public void add()
{
threadLocalField.set(1 + threadLocalField.get());
}
}
/**
* @auther zzyy
.【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理
自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用
try-finally 块进行回收。
*/
public class ThreadLocalDemo2
{
public static void main(String[] args) throws InterruptedException
{
MyData myData = new MyData();
ExecutorService threadPool = Executors.newFixedThreadPool(3);
try
{
for (int i = 0; i < 10; i++) {
threadPool.submit(() -> {
try {
Integer beforeInt = myData.threadLocalField.get();
myData.add();
Integer afterInt = myData.threadLocalField.get();
System.out.println(Thread.currentThread().getName()+"\t"+"beforeInt:"+beforeInt+"\t afterInt: "+afterInt);
} finally {
myData.threadLocalField.remove();
}
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
通过上面代码总结
②. ThreadLocal源码分析
①. Thread 、 ThreadLocal 、ThreadLocalMap关系
①. Thread和ThreadLocal
②. ThreadLocal和ThreadLocalMap
三者总概括
1、 Thread类中有一个ThreadLocal.ThreadLocalMap threadLocals = null的变量,这个ThreadLocal相当于是Thread类和ThreadLocalMap的桥梁,在ThreadLocal中有静态内部类ThreadLocalMap,ThreadLocalMap中有Entry数组
2、 当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放
3、 t.threadLocals = new ThreadLocalMap(this, firstValue) 如下这行代码,可以知道每个线程都会创建一个ThreadLocalMap对象,每个线程都有自己的变量副本
ThreadLocal 几个核心方法
核心代码
//核心代码说明
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}
②. get方法详解
先获取当前线程的ThreadLocalMap变量,如果存在则返回值,不存在则创建并返回初始值
/**
* 返回当前线程中保存ThreadLocal的值
* 如果当前线程没有此ThreadLocal变量,
* 则它会通过调用{@link #initialValue} 方法进行初始化值
*
* @return 返回当前线程对应此ThreadLocal的值
*/
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null) {
// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
ThreadLocalMap.Entry e = map.getEntry(this);
// 对e进行判空
if (e != null) {
@SuppressWarnings("unchecked")
// 获取存储实体 e 对应的 value值
// 即为我们想要的当前线程对应此ThreadLocal的值
T result = (T)e.value;
return result;
}
}
/*
初始化 : 有两种情况有执行当前代码
第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象
第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry
*/
return setInitialValue();
}
/**
* 初始化
*
* @return the initial value 初始化后的值
*/
private T setInitialValue() {
// 调用initialValue获取初始化的值
// 此方法可以被子类重写, 如果不重写默认返回null
T value = initialValue();
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
// 返回设置的值value
return value;
}
③. set方法详解
①. 首先获取当前线程,并根据当前线程获取一个Map
②. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)
③. 如果Map为空,则给该线程创建 Map,并设置初始值
/**
* 设置当前线程对应的ThreadLocal的值
*
* @param value 将要保存在当前线程对应的ThreadLocal的值
*/
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
}
/**
* 获取当前线程Thread对应维护的ThreadLocalMap
*
* @param t the current thread 当前线程
* @return the map 对应维护的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
*创建当前线程Thread对应维护的ThreadLocalMap
*
* @param t 当前线程
* @param firstValue 存放到map中第一个entry的值
*/
void createMap(Thread t, T firstValue) {
//这里的this是调用此方法的threadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/*
* firstKey : 本ThreadLocal实例(this)
* firstValue : 要保存的线程本地变量
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化table
table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
//计算索引(重点代码)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//设置值
table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
size = 1;
//设置阈值
setThreshold(INITIAL_CAPACITY);
}
④. remove方法详解
①. 首先获取当前线程,并根据当前线程获取一个Map
②. 如果获取的Map不为空,则移除当前ThreadLocal对象对应的entry
/**
* 删除当前线程中保存的ThreadLocal对应的实体entry
*/
public void remove() {
// 获取当前线程对象中维护的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
// 如果此map存在
if (m != null)
// 存在则调用map.remove
// 以当前ThreadLocal为key删除对应的实体entry
m.remove(this);
}
总结
③ 引用分为 强引用 软引用 虚引用 弱引用
内存泄漏问题
首先理解什么是内存泄漏
我们来看下引用架构
来看下这个finalize()方法的说明
强引用
什么是强引用
package com.bilibili.juc.tl;
import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
class MyObject
{
//这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
@Override
protected void finalize() throws Throwable
{
// finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
System.out.println("-------invoke finalize method~!!!");
}
}
/**
* @auther zzyy
*/
public class ReferenceDemo
{
public static void main(String[] args)
{
strongReference();
}
/***
* 强引用
*/
private static void strongReference()
{
MyObject myObject = new MyObject();
System.out.println("gc before: "+myObject);
myObject = null;
System.gc();//人工开启GC,一般不用
//暂停毫秒
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("gc after: "+myObject);
}
}
软引用
什么是软引用
package com.bilibili.juc.tl;
import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
class MyObject
{
//这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
@Override
protected void finalize() throws Throwable
{
// finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
System.out.println("-------invoke finalize method~!!!");
}
}
/**
* @auther zzyy
*/
public class ReferenceDemo
{
public static void main(String[] args) {
softReference();
}
/**
* 软引用
*/
private static void softReference()
{
SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
//System.out.println("-----softReference:"+softReference.get());
System.gc();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----gc after内存够用: "+softReference.get());
try
{
byte[] bytes = new byte[20 * 1024 * 1024];//20MB对象
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("-----gc after内存不够: "+softReference.get());
}
}
}
弱引用
什么是弱引用
package com.bilibili.juc.tl;
import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
class MyObject
{
//这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
@Override
protected void finalize() throws Throwable
{
// finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
System.out.println("-------invoke finalize method~!!!");
}
}
/**
* @auther zzyy
*/
public class ReferenceDemo
{
public static void main(String[] args)
{
weakReference();
}
/**
* 弱引用
*/
private static void weakReference()
{
WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
System.out.println("-----gc before 内存够用: "+weakReference.get());
System.gc();
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----gc after 内存够用: "+weakReference.get());
}
}
软引用 和弱引用使用场景
虚引用
什么是虚引用
PhantomReference类
ReferenceQueue类
package com.bilibili.juc.tl;
import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
class MyObject
{
//这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
@Override
protected void finalize() throws Throwable
{
// finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
System.out.println("-------invoke finalize method~!!!");
}
}
/**
* @auther zzyy
*/
public class ReferenceDemo
{
public static void main(String[] args)
{
MyObject myObject = new MyObject();
ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject,referenceQueue);
//System.out.println(phantomReference.get());
List<byte[]> list = new ArrayList<>();
new Thread(() -> {
while (true){
list.add(new byte[1 * 1024 * 1024]);
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(phantomReference.get()+"\t"+"list add ok");
}
},"t1").start();
new Thread(() -> {
while (true){
Reference<? extends MyObject> reference = referenceQueue.poll();
if(reference != null){
System.out.println("-----有虚对象回收加入了队列");
break;
}
}
},"t2").start();
}
}
④. ThreadLocal源码分析
在回过头来看下ThreadLocalMap 为什么使用弱引用
先来看下这几行代码
public class T1
{
volatile boolean flag;
public static void main(String[] args)
{
ThreadLocal<String> tl = new ThreadLocal<>(); //line1
tl.set("zzyybs@126.com"); //line2
tl.get(); //line3
}
}
为什么使用弱引用原因
①. 当function01方法执行完毕后,栈帧销毁强引用 tl 也就没有了。但此时线程的ThreadLocalMap里某个entry的key引用还指向这个对象
原因(Thread 和 ThreadLocal 是2个不同的类 )
②. 若这个key引用是强引用,就会导致key指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏
③. 若这个key引用是弱引用就大概率会减少内存泄漏的问题(还有一个key为null的雷)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向为null
为什么会有kev为null的entry
清除脏entry原理分析
①. ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如正好用在线程池),这些key为null的Entry的value就会一直存在一条强引用链
②. 虽然弱引用,保证了key指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为null时才会去回收整个entry、value
③. 因此弱引用不能100%保证内存不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug
④. 如果当前thread运行结束,threadLocal,threadLocalMap, Entry没有引用链可达,在垃圾回收的时候都会被系统进行回收
⑤. 但在实际使用中我们有时候会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们小心
⑥. 出现内存泄漏的真实原因 (1). 没有手动删除这个Entry (2). CurrentThread依然运行
set、get方法会去检查所有键为null的Entry对象
①. set( )
②. get( )
③. remove( )
结论(在finally后面调用remove方法)
ThreadLocal总结
①. ThreadLocal本地线程变量,以空间换时间,线程自带的变量副本,人手一份,避免了线程安全问题
②. 每个线程持有一个只属于自己的专属Map并维护了Thread Local对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题
③. ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题
④. 都会通过expungeStaleEntry,cleanSomeSlots, replace StaleEntry这三个方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏,属于安全加固的方法
⑤. 用完之后一定要remove操作