前言:进程中存在多个线程,该进程中多线程可以资源共享,但是多个线程多统一个资源进行修改时,会造成线程安全问题,我们有没有办法解决呢?于是有了ThreadLocal,我的理解为每一个线程创建一个副本,每个线程专用。
案例代码
package ThreadLocal;
import ExecutorDemo.newFixedThreadPool.FixedThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description:
* @author: shu
* @createDate: 2022/11/2 20:19
* @version: 1.0
*/
public class ThreadLocalDemo {
static class Foo{
// 计数器
static final AtomicInteger AMOUT=new AtomicInteger();
int index=0;
int bar=10;
public Foo() {
this.index=AMOUT.getAndIncrement();
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getBar() {
return bar;
}
public void setBar(int bar) {
this.bar = bar;
}
@Override
public String toString() {
return "Foo{" +
"index=" + index +
", bar=" + bar +
'}';
}
}
private static final ThreadLocal<Foo> FOO_THREAD_LOCAL=new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 线程池
ExecutorService singleThreadExecutor = Executors.newFixedThreadPool(5);
// 批量添加线程
for (int i = 0; i < 5; i++) {
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
// 本地线程变量中没有值
if(FOO_THREAD_LOCAL.get()==null){
FOO_THREAD_LOCAL.set(new Foo());
}
System.out.println("FOO_THREAD_LOCAL 初始化的值"+FOO_THREAD_LOCAL.get());
for (int j = 0; j < 10; j++) {
Foo foo = FOO_THREAD_LOCAL.get();
foo.setBar(foo.getBar()+1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("FOO_THREAD_LOCAL 累加的值"+FOO_THREAD_LOCAL.get());
// 删除当前本地线程变量绑定的值
FOO_THREAD_LOCAL.remove();
}
});
}
Thread.sleep(1000);
// 线程池销毁
singleThreadExecutor.shutdown();;
}
}
在线程本地变量(FOO_THREAD_LOCAL)中,每一个线程都绑定了一个独立的值(Foo对象),这些值对象是线程的私有财产,可以理解为线程的本地值,线程的每一次操作都是在自己的同一个本地值上进行的,从例子中线程本地值的index始终一致可以看出,每个线程操作的是同一个Foo对象。
ThreadLocal是解决线程安全问题的一个较好的方案,它通过为每个线程提供一个独立的本地值去解决并发访问的冲突问题。
线程隔离
ThreadLocal的主要价值在于线程隔离,ThreadLocal中的数据只属于当前线程,其本地值对别的线程是不可见的,在多线程环境下,可以防止自己的变量被其他线程篡改。
场景:可以为每个线程绑定一个用户会话信息、数据库连接、HTTP请求等,这样一个线程所有调用到的处理函数都可以非常方便地访问这些资源。
private static final ThreadLocal threadSession = new ThreadLocal();
//获取会话,如果Session的使用方式为共享而不是独占,在
//这种情况下,Session是多线程共享使用的,
//如果某个线程使用完成之后直接将Session关闭,
//其他线程在操作Session时就会报错。
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
跨函数传递数据
通常用于同一个线程内,跨类、跨方法传递数据时,如果不用ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度。
场景:可以为每个线程绑定一个Session(用户会话)信息,这样一个线程所有调用到的代码都可以非常方便地访问这个本地会话,而不需要通过参数传递。
public class SessionHolder
{
// session id,线程本地变量
private static final ThreadLocal<String> sidLocal =
new ThreadLocal<>("sidLocal");
// 用户信息,线程本地变量
private static final ThreadLocal<UserDTO> sessionUserLocal =
new ThreadLocal<>("sessionUserLocal");
// session,线程本地变量
private static final ThreadLocal<HttpSession> sessionLocal =
new ThreadLocal<>("sessionLocal");
/**
*保存session在线程本地变量中
*/
public static void setSession(HttpSession session)
{
sessionLocal.set(session);
}
/**
* 取得绑定在线程本地变量中的session
*/
public static HttpSession getSession()
{
HttpSession session = sessionLocal.get();
Assert.notNull(session, "session未设置");
return session;
}
}
早期Jdk
注意:key:为当前线程,value:ThreadLocal绑定的值
Jdk1.8
总结
优势
get()方法
get()方法用于获取线程本地变量在当前线程的ThreadLocalMap中对应的值,相当于获取线程本地值
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程对象的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 判断
if (map != null) {
// 已当前线程为key,获取value
ThreadLocalMap.Entry e = map.getEntry(this);
// 存在,就返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 没有存在,就初始值
return setInitialValue();
}
private T setInitialValue() {
// 调用初始化函数,NULL
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// 创建新ThreadLocalMap成员
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set()方法
set(T value)方法用于设置线程本地变量在当前线程的ThreadLocalMap中对应的值,相当于设置线程本地值
public void set(T value) {
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap 成员
ThreadLocalMap map = getMap(t);
//判断map是否存在
if (map != null)
{
//value被绑定到threadLocal实例
map.set(this, value);
}
else
{
// 如果当前线程没有ThreadLocalMap成员实例
// 创建一个ThreadLocalMap实例,然后作为成员关联到t(thread实例)
createMap(t, value);
}
}
// 获取线程t的ThreadLocalMap成员
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 线程t创建一个ThreadLocalMap成员
// 并为新的Map成员设置第一个“Key-Value对”,Key为当前的ThreadLocal实例
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
remove()方法
remove()方法用于在当前线程的ThreadLocalMap中移除“线程本地变量”所对应的值。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
initialValue()方法
当线程本地变量在当前线程的ThreadLocalMap中尚未绑定值时,initialValue()方法用于获取初始值。
protected T initialValue() {
return null;
}
初始化方法
ThreadLocal<Foo> LOCAL_FOO = ThreadLocal.withInitial(() -> new Foo());
//ThreadLocal工厂方法可以设置本地变量初始值钩子函数
public static <S> ThreadLocal<S> withInitial(
Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
//内部静态子类
//继承了ThreadLocal,重写了initialValue()方法,返回钩子函数的值作为初始值
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
//保存钩子函数
private final Supplier<? extends T> supplier;
//传入钩子函数
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get(); //返回钩子函数的值作为初始值
}
}
ThreadLocal的操作都是基于ThreadLocalMap展开的,而ThreadLocalMap是ThreadLocal的一个静态内部类,其实现了一套简单的Map结构(比HashMap简单)。
成员变量
public class ThreadLocal<T> {
static class ThreadLocalMap {
// Map的条目数组,作为哈希表使用
private Entry[] table;
// Map的条目初始容量16
private static final int INITIAL_CAPACITY = 16;
// Map的条目数量
private int size = 0;
// 扩容因子
private int threshold;
// Map的条目类型,一个静态的内部类
// Entry 继承子WeakReference, Key为ThreadLocal实例
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; //条目的值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
构造器
// 构造一个初始包含(firstKey, firstValue)的新映射。
// threadlocalmap是惰性构造的,因此只有在至少有一个条目要放入时才创建一个。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化节点容量16
table = new Entry[INITIAL_CAPACITY];
// 哈希运算,避免哈希冲突
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 设置值
table[i] = new Entry(firstKey, firstValue);
// 大小
size = 1;
// 扩容2/3
setThreshold(INITIAL_CAPACITY);
}
// 从给定的父映射中构造一个包含所有Inheritable ThreadLocals的新映射。
// 仅由createInheritedMap调用。
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
getEntry() 方法
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
// 找不到,直接返回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)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
set()方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//根据key的HashCode,找到key在数组上的槽点i
int i = key.threadLocalHashCode & (len-1);
// 从槽点i开始向后循环搜索,找空余槽点(空余位置)或者找现有槽点
// 若没有现有槽点,则必定有空余槽点,因为没有空间时会扩容
for (Entry e = tab[i]; e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//找到现有槽点:Key值为ThreadLocal实例
if (k == key) {
e.value = value;
return;
}
//找到异常槽点:槽点被GC掉,重设Key值和Value值
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//没有找到现有的槽点,增加新的Entry
tab[i] = new Entry(key, value);
//设置ThreadLocal数量
int sz = ++size;
//清理Key为null的无效Entry
//没有可清理的Entry,并且现有条目数量大于扩容因子值,进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
remove()方法
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;
}
}
}
前提知识
强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
原因
public void funcA()
{
//创建一个线程本地变量
ThreadLocal local = new ThreadLocal<Integer>();
//设置值
local.set(100);
//获取值
local.get();
//函数末尾
}
一句话:如果是强引用,当方法执行完,实例本该销毁,但是迟迟不被GC清除没会造成严重的内存泄漏
使用总结
//推荐使用static final线程本地变量
private static final ThreadLocal<Foo> FOO_THREAD_LOCAL = new ThreadLocal<Foo>();
// 删除当前本地线程变量绑定的值
FOO_THREAD_LOCAL.remove();