温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

java中ThreadLocal有什么作用

发布时间:2021-10-28 11:49:07 来源:亿速云 阅读:165 作者:iii 栏目:编程语言

这篇文章主要介绍“java中ThreadLocal有什么作用”,在日常操作中,相信很多人在java中ThreadLocal有什么作用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”java中ThreadLocal有什么作用”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

谈一谈不常见却又不可少的ThreadLocal

内存模型中所有变量存储在主内存中,当一个线程中要使用某个变量时,需要从主内存复制该变量到其线程内才能操作,此时线程中操作的是主内存变量的副本,操作完成后再刷回主内存。刷回的实质就是变量赋值

如果多个线程访问同一个变量时,每个线程都具有一个副本,操作完毕后都会刷回主内存,刷回时间存在先后,则赋值有先后,当然后者会覆盖前者,这是造成可见性问题的次要原因。

引入以上知识点后,再来说明ThreadLocal。一个线程想使用某个变量,于是从主内存复制该共享变量到线程内部中。使用完毕后想再下次再次使用该变量时,得到的变量副本是上次使用的副本,而不是从主内存的变量再次复制过来的副本,并且不想让其他线程影响到该变量。这就是ThreadLocal的目的,其实现不是通过共享变量这种方式实现的,详细内容下面介绍

目的很明确,但是身处JAVA内存模型中要遵循内存模型规范,下面看看JDK是如何即满足内存模型规范,又满足ThreadLocal目的。

满足内存模型

这点很简单,就是你该怎么样还怎么样,仍然受你管辖,该复制就复制,该刷回就刷回,不可见还是会造成不可见。

满足ThreadLocal目的

多个线程都能访问的变量才叫共享变量,如果控制变量的访问方式,使其他线程线程不能访问就可以了。控制方式就是将线程与变量的一一对应,将该变量的访问入口控制到只有该线程即可,JDK中的做法就是让线程持有这个变量(绑定到线程本身)

线程可以绑定变量,但是并不知道需要绑定多少个,于是将这个存储功能还是交给专门的数据结构—>Map。并且还专门设计了一个用来访问这个Map的工具,这个工具就是ThreadLocal。并且这个Map的key为ThreadLocal实例的引用地址,value存储真正的变量。

这样设计就到达目的了。ThreadLocal构建时接收个泛型告诉你存储的变量是一个对象类型。

ThreadLocal设计

核心Map设计

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
...
}

这里Map的Entry被设计为弱引用。

内存存储图示如下

java中ThreadLocal有什么作用

弱引用WeakReference

WeakReference是Java语言规范中为了区别直接的对象引用(程序中通过构造函数声明出来的对象引用)而定义的另外一种引用关系。WeakReference标志性的特点是:reference实例不会影响到被应用对象的GC回收行为(即只要对象被除WeakReference对象之外所有的对象解除引用后,该对象便可以被GC回收),只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。

这里使用弱引用的原因:ThreadLocal目的就是达到变量只能自己访问别的线程不能访问的目的,Map设计的key为ThreadLocal实例的引用地址,value为变量,当ThreadLocal定义处的实例被销毁时,如果Map中Entity还强引用该实例时,就会导致该实例不会被回收,而外部因为引用对象被销毁而不能操作该地址,于是会造成内存泄露。当Map的Key设计为弱引用时,如果外部实例被销毁,即使弱引用还存在,Map中的对应的ThreadLocal也会被回收,则key=null。

使用弱引用后,虽然Key可以被回收了,但是value还是存在的,还会存在临时内存泄露问题,图示如下

临时内存泄露图示

java中ThreadLocal有什么作用

对于临时内存泄露,ThreadLocal在后续get、set操作时,会清理掉Map中key=null的Entity节点,不过再清理之前Value一直存在,如果继续使用Value很容易出现问题

ThreadLocal由于一个实例存储一个对象,因此一般会被定义为static的,防止ThreadLocal所在类实例被多次创建时ThreadLocal也被多次创建,

另外ThreadLocal也会定义为final的,防止实例被覆盖掉,也就是只在实例化时new一次

Map的持有状态

在Thread类定义中这样定义ThreadLocalMap

public class Thread implements Runnable {
  ...
  ThreadLocal.ThreadLocalMap threadLocals = null;
  ...
}

也就是ThreadLocalMap是线程类Thread持有的对象,一个线程持有一个ThreadLocalMap,只有线程存在,则ThreadLocalMap必存在。这也是ThreadLocal对象销毁后,value还存在的原因,ThreadLocalMap还被Thread强引用。只有线程销毁时,ThreadLocalMap才会随之销毁。
 

ThreadLocal的使用

private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();  // 接收泛型
threadLocal.set(1);  // 对应泛型
Integer var=threadLocal.get();

ThreadLocal底层原理

设置变量 Set

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
// 从当前线程中获取一个ThreadLocalMap实例
    ThreadLocalMap map = getMap(t);
// map存在则放入key,value  key为当前ThreadLocal对象引用
    if (map != null)
        map.set(this, value);
    // map不存在则构建,同样放入key,value  
    else
        createMap(t, value);
}

ThreadLocalMap实例的获取就是从Thread中获取的,也就是上面的持有状态,拿到之后就可以向Map结构中存储key,value了,前面也说过了,这里key存在的ThreadLocal实例的引用地址,value存在的变量的引用地址。一个线程可以存储多个ThreadLocal实例。

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

就是获取当前线程持有的ThreadLocalMap

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

给当前线程初始化ThreadLocalMap实例,并set初始值。key为ThreadLocal实例的引用地址

获取变量Get

public T get() {
    Thread t = Thread.currentThread();  //① 获取当前线程t
    ThreadLocalMap map = getMap(t); //②从当前线程中获取一个ThreadLocalMap
    if (map != null) { //③ ThreadLocalMap不为null则从map中获取value ;     
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();//④ThreadLocalMap为null则调用setInitialValue() 返回—初始化的值
}

在最后一个return之前的代码就是从当前线程的ThreadLocalMap中通过Map的get方法获取变量引用的过程。这是Map的基本用用法。

存在以下情况则需要获取初始的默认值,这是一个对外开放的功能,就是可以指定ThreadLocal对应的变量的初始默认值,默认为null,可以被重写

  • ThreadLocalMap未实例化

  • ThreadLocalMap已实例化,但是还没有Set变量

设置默认的初始值
protected T initialValue() {
    return null;
}
private T setInitialValue() {
    T value = initialValue(); //① 初始化为null
    Thread t = Thread.currentThread(); //② 获取当前线程
    ThreadLocalMap map = getMap(t); //③ 从当前线程中获取一个ThreadLocalMap
    if (map != null)
        map.set(this, value); //④ ThreadLocalMap不为null,则存储key=this,value=value
    else
        createMap(t, value);//⑤ ThreadLocalMap为null,则为当前线程创建一个ThreadLocalMap并初始化值
    return value;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

代码流程也很简单,只是为了功能优化了,进行的统一封装

  • ThreadLocalMap的惰性实例化

  • 获取ThreadLocalMap时需要Thread,而ThreadLocal只是访问控制工具,于是需要打通Thread来获取ThreadLocalMap

移除
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

主动移除是个好的处理方式,在不使用变量时应该主动移除

到这里ThreadLocal本身的功能已经介绍完了,可以理解为变量访问工具,这个变量的访问被控制到只能当前线程有权限访问,其他线程无权限。

代码示例

简单示例

package cn.tinyice.demo.thread;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
* ThreadLocalDemo
*
* @author Tinyice
*/
public class ThreadLocalDemo {

    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    private static final Random random=new Random();

    /**
     * 内存模型示例
     */
    private int mod=0;

    public ThreadLocalDemo(int mod) {
        this.mod = mod;
    }

    public int getMod() {
        return mod;
    }

    public void add() {
        mod+=1;
        // 每个线程的值是不一样的
        threadLocal.set(get() + random.nextInt(10));
    }

    public Integer get() {
        Integer integer = threadLocal.get();
        return null == integer ? 0 : integer;
    }

    public static void main(String[] args) {
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(0);
        for (int i = 0; i < 1000; i++) {
            new ThreadLocalThread(threadLocalDemo).start();
        }
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("------------------------------------------------");
        System.out.println(String.format("threadId=%d threadLocal value=%d mod=%d", Thread.currentThread().getId(), threadLocalDemo.get(),threadLocalDemo.getMod()));
    }

}

class ThreadLocalThread extends Thread {

    private ThreadLocalDemo threadLocalDemo;

    public ThreadLocalThread( ThreadLocalDemo threadLocalDemo) {
        this.threadLocalDemo = threadLocalDemo;
    }

    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadLocalDemo.add();
        System.out.println(String.format("threadId=%d threadLocal value=%d", Thread.currentThread().getId(), threadLocalDemo.get()));
    }
}

控制台:

threadId=853 threadLocal value=3
threadId=848 threadLocal value=9
threadId=808 threadLocal value=9
threadId=121 threadLocal value=8
------------------------------------------------
threadId=1 threadLocal value=0 mod=999

Process finished with exit code 0

mod=999 是可见性问题造成,而value值都不一样则说明不同线程的变量值不一样。

Spring示例

public abstract class TransactionSynchronizationManager {
    private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");
...
}
public final class DateTimeContextHolder {
	private static final ThreadLocal<DateTimeContext> dateTimeContextHolder = new NamedThreadLocal<>("DateTimeContext");
       ...
}

到此,关于“java中ThreadLocal有什么作用”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI