ThreadLocal详解

本文详细介绍了Java中的ThreadLocal,包括其作用、原理、set、get和remove方法的源码分析,以及ThreadLocal的优缺点。示例代码展示了ThreadLocal如何确保线程局部变量的独立性,避免并发问题。同时提到了ThreadLocal可能导致的内存泄漏风险,强调了使用完后应及时调用remove方法。
摘要由CSDN通过智能技术生成

1.ThreadLocal概述

1.1ThreadLocal简介

ThreadLocal本地线程,也叫线程局部变量。
为什么需要线程局部变量,就是为了保证多线程下各个线程变量的独立性,避免多线程并发环境全局变量被其他线程篡改,造成数据异常。举个例子,A,B线程同时请求支付操作,A获取用户账户信息,在A还未进行支付操作之前,B线程把账户信息修改为B的,此时A支付操作,就会使用B的账户信息,导致数据不一致,产生脏数据。当然了通过加锁也可以实现,本文主要讲ThreadLocal方式实现,毕竟加锁后多线程竞争锁资源内存消耗还是不较大的。

1.2 ThreadLocal验证

验证ThreadLocal多线程下各自使用线程局部变量互不影响。

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.zrj.unit.entity.User;

import java.util.concurrent.*;

/**
 * ThreadLocal:线程局部变量
 *
 * @author zrj
 * @since 2021/8/14
 **/
public class ThreadLocalTest {
    // 自定义线程名称
    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("thread-local-pool-%d").build();
    // 定义线程池
    private static ExecutorService pool = new ThreadPoolExecutor(30, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
    // 定义ThreadLocal
    private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();

    /**
     * 开十个线程分别处理userThreadLocal互不影响
     * 各自设置各自的User对象,各自删除各自的remove
     */
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            pool.execute(() -> {
                String currentThreadName = Thread.currentThread().getName();
                userThreadLocal.set(User.builder().name(currentThreadName).build());
                System.out.println(currentThreadName + " before:" + userThreadLocal.get());
                userThreadLocal.remove();
                System.out.println(currentThreadName + " after:" + userThreadLocal.get());
            });
        }

        try {
            //通知线程结束
            pool.shutdown();
            //中止任务执行,防止长时间等待
            pool.awaitTermination(5000, TimeUnit.MILLISECONDS);
            //shutdownNow方法的作用是向所有执行中的线程发出interrupted以中止线程的运行。这时,各个线程会抛出InterruptedException异常
            pool.shutdownNow();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程是否结束:" + pool.isShutdown());
    }
}

输出结果

thread-local-pool-5 before:User(name=thread-local-pool-5, age=null)
thread-local-pool-6 before:User(name=thread-local-pool-6, age=null)
thread-local-pool-4 before:User(name=thread-local-pool-4, age=null)
thread-local-pool-1 before:User(name=thread-local-pool-1, age=null)
thread-local-pool-9 before:User(name=thread-local-pool-9, age=null)
thread-local-pool-2 before:User(name=thread-local-pool-2, age=null)
thread-local-pool-3 before:User(name=thread-local-pool-3, age=null)
thread-local-pool-8 before:User(name=thread-local-pool-8, age=null)
thread-local-pool-3 after:null
thread-local-pool-2 after:null
thread-local-pool-9 after:null
thread-local-pool-1 after:null
thread-local-pool-4 after:null
thread-local-pool-6 after:null
thread-local-pool-0 before:User(name=thread-local-pool-0, age=null)
thread-local-pool-5 after:null
thread-local-pool-7 before:User(name=thread-local-pool-7, age=null)
thread-local-pool-0 after:null
thread-local-pool-8 after:null
thread-local-pool-7 after:null
线程是否结束:true

2.ThreadLocal原理分析

ThreadLocal优秀之处在于它每次set,get,remove之前都会先获取当前线程Thread对象,通过当前线程Thread获取ThreadLocalMap在进行局部变量对象的操作。这就保证了不同线程取到的都是线程变量的副本,多线程之间局部变量操作互不影响。
由于ThreadLocalMap中Key是ThreadLocal对象,是弱引用,但是value是局部变量,强引用,如果使用完不及时remove则会导致内存泄漏,这也就是为什么ThreadLocal存在内存泄漏的风险。画张图吧,更清晰点。
对象的引用都是在栈内存Stack,实例对象在堆内存Heap。
在这里插入图片描述

2.1 set方法源码

注意,网上有些文章说ThreadlocalMap使用线程作为key这个很扯,源码里面写的很清楚ThreadLocal的set方法是先获取当前线程Thread对象,然后根据当前线程对象Thread获取ThreadLocalMap,map的key是当前ThreadLocal对象,value是局部变量。
另外ThreadLocal在Thread对象中是一个属性成员变量,是Threadlocal 对象的内部类,就是让使用者知道ThreadLocalMap就只做保存线程局部变量这一件事的。

# ThreadlocalThreadlocal.class 
public void set(T value) {
  //获取当前线程Thread,就是上图画的Thread 引用
  Thread t = Thread.currentThread(); 
  //Thread类有个成员变量ThreadlocalMap,拿到这个Map
  ThreadLocalMap map = getMap(t);
  if (map != null)
    //this指的就是Threadlocal对象
    map.set(this, value);
  else
    createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
  //获取线程的ThreadLocalMap
  return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
  //初始化
  t.threadLocals = new ThreadLocalMap(this, firstValue);
}

# ThreadThread.class
public class Thread implements Runnable {
    //每个线程都有自己的ThreadLocalMap 成员变量
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

2.2 get方法源码

在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。

public T get() {
    //(1)获取当前线程
    Thread t = Thread.currentThread();
    //(2)获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
    return setInitialValue();
}

private T setInitialValue() {
    //protected T initialValue() {return null;}
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
    return value;
}

2.3 remove方法源码

remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量。

public void remove() {
    //获取当前线程绑定的threadLocals
     ThreadLocalMap m = getMap(Thread.currentThread());
     //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
     if (m != null)
         m.remove(this);
 }

3.ThreadLocal优缺点

优点:能实现线程局部变量,避免多线程并发问题。
缺点:ThreadLocal不支持继承性。

4. InteritableThreadLocal

在上面说到的ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能,下面是该类的源码

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

从上面代码可以看出,InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个方法。其中createMap方法在被调用(当前线程调用set方法时得到的map为null的时候需要调用该方法)的时候,创建的是inheritableThreadLocal而不是threadLocals。同理,getMap方法在当前调用者线程调用get方法的时候返回的也不是threadLocals而是inheritableThreadLocal。

5. ThreadLocal内存泄漏风险

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值