java线程,ThreadLocal类使用,源码分析,面试题

ThreadLocal: 提供线程局部变量。与线程类Thread同包,将Thread类下的threadLocals(threadLocals作用域是default,同包内可以访问)对象提供对外操作方法。设置引用对象,设置key-value,根据key(ThreadLocal)获取值。

public class MyThreeLocal {
    
    

    /**
     * ThreadLocal 提供线程局部变量threadLocals,已threadLocal本身为key,设置ThreadLocalMap(key-val格式,ThreadLocal的内部类)给线程Thread类下的threadLocals,
     * 并提供设置对象set,获取对象的方法get
     */
    private static final ThreadLocal<Cache> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
    
    
        for(int i = 0 ;i < 100;i ++){
    
    
            new Thread(() -> {
    
    
               try {
    
    
                   //获取到线程的threadLocals(map),已本身(threadLocal)为key,存入到线程的threadLocals中
                   //如果线程的threadLocals为null,则创建一个
                   threadLocal.set(new Cache(Thread.currentThread().getName()));
                   //防止引用多次传递,在方法中直接通过ThreadLocal获取set进去的值
                   work1();
                   work2();
               }finally {
    
    
                   //清除资源
                   threadLocal.remove();
               }
            }).start();
        }
    }

    /**
     * 避免多次传递,直接通过线程在ThreadLocal中获取Cache对象
     * (就是已ThreadLocal当门户(门户的意思是线程的threadLocals对象,本身不同包是访问不了的,
     *      * 通过ThreadLocal提供的方法可以去访问)和key去线程的threadLocals获取对象)
     */
    private static void work1(){
    
    
        //获取Cache,已threadLocal对象当key,去线程的本地变量threadLocals中获取值,threadLocal相当于一个对Thread内threadLocals访问的门户
        //本来不可访问的属性,通过ThreadLocal类(ThreadLocal和Thread同一包下,可以访问默认访问区间的属性)
        Cache cache = threadLocal.get();
        System.out.println(Thread.currentThread().getName() + "===work1获取ThreadLocal:" + cache.getName());
    }

    /**
     * 再次获取
     */
    private static void work2(){
    
    
        Cache cache = threadLocal.get();
        System.out.println(Thread.currentThread().getName() + "===work2获取ThreadLocal:" + cache.getName());
    }

}

class Cache{
    
    

    private String name;


    public String getName() {
    
    
        return name;
    }

    public Cache(String name){
    
    
        this.name = name;
    }
}

源码:

ThreadLocal.set(T value): 设置值/创建并设置threadLocals的引用对象和值,支持泛型,创建ThreadLocal时指定。

/**
 * ThreadLocal set方法源码
 */
public void set(T value) {
    
    
    //1:获取到当前线程
    Thread t = Thread.currentThread();
    //2:从线程t中获取threadLocals,threadLocals对象属性是ThreadLocal内部类 :ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    //3:不为null则已当前对象(创建的ThreadLocal对象,这里的key是this当前对象)为key,把设置的value放到线程的threadLocals中。
    //为null则创建一个ThreadLocalMap,让当前线程的threadLocals指向新建的ThreadLocalMap对象
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

/**
 * createMap 方法源码
 */
void createMap(Thread t, T firstValue) {
    
    
       t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal.get():获取储存在线程threadLocals对象内的值,ThreadLocal对象为key。

 /**
  * get方法
  */
public T get() {
    
    
    //1:获取当前线程
    Thread t = Thread.currentThread();
    //2: 从线程中取出threadLocals
    ThreadLocalMap map = getMap(t);
    //3:已ThreadLocal对象为key获取值,返回
    if (map != null) {
    
    
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
    
    
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //4:如果map为null,设置threadLocals,设置一个null值,然后返回null
    return setInitialValue();
}

/**
 * setInitialValue 方法
 */
private T setInitialValue() {
    
    
       T value = initialValue();
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null)
           map.set(this, value);
       else
           createMap(t, value);
       return value;
 }

/**
 * initialValue 方法 返回null
 */
protected T initialValue() {
    
    
       return null;
}

ThreadLocal.remove():移除key

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

ThreadLocal总结: ThreadLocal是一个操作线程内存的工具,并不负责存值,而是负责创建ThreadLocalMap对象,指向线程的内存空间threadLocals,ThreadLocalMap是key-val格式,key为ThreadLocal实例,ThreadLocalMap线程私有,是保存在每个线程对象上的。

=====================================================================

ThreadLoca面试相关:

以下内容转载:https://blog.csdn.net/u012877396/article/details/102715845

1.ThreadLocal 是什么?

ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,适用于各个线程不共享变量值的操作。

2.ThreadLocal 工作原理是什么?

ThreadLocal 原理:每个线程的内部都维护了一个 ThreadLocalMap,它是一个 Map(key,value)数据格式,key 是一个弱引用,也就是 ThreadLocal 本身,而 value 存的是线程变量的值。

也就是说 ThreadLocal 本身并不存储线程的变量值,它只是一个工具,用来维护线程内部的 Map,帮助存和取变量。

3.ThreadLocal 如何解决 Hash 冲突?

与 HashMap 不同,ThreadLocalMap 结构非常简单,没有 next 引用,也就是说 ThreadLocalMap 中解决 Hash 冲突的方式并非链表的方式,而是采用线性探测的方式。所谓线性探测,就是根据初始 key 的 hashcode 值确定元素在 table 数组中的位置,如果发现这个位置上已经被其他的 key 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

源代码实现如下:

/
 * Increment i modulo len.
 */
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

/
 * Decrement i modulo len.
 */
private static int prevIndex(int i, int len) {
    return ((i - 1 >= 0) ? i - 1 : len - 1);
}

1234567891011121314

4.ThreadLocal 的内存泄露是怎么回事?

ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。

5.为什么 ThreadLocalMap 的 key 是弱引用?

我们知道 ThreadLocalMap 中的 key 是弱引用,而 value 是强引用才会导致内存泄露的问题,至于为什么要这样设计,这样分为两种情况来讨论:

key 使用强引用:这样会导致一个问题,引用的 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有手动删除,ThreadLocal 不会被回收,则会导致内存泄漏。
key 使用弱引用:这样的话,引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候会被清除。
比较以上两种情况,我们可以发现:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障,弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候被清除,算是最优的解决方案。

6.ThreadLocal 的应用场景有哪些?

ThreadLocal 适用于独立变量副本的情况,比如 Hibernate 的 session 获取场景。

private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();

public static Session getCurrentSession(){
    Session session =  threadLocal.get();
    try {
        if(session ==null&&!session.isOpen()){
            //...
        }
        threadLocal.set(session);
    } catch (Exception e) {
        // TODO: handle exception
    }
    return session;
}

123456789101112131415

比如:使用ThreadLocal保证DateFormat线程安全

public class threadLocalTest {

    public static ThreadLocal<DateFormat> DATE_FORMAT_THREAD_LOCAL=new ThreadLocal<DateFormat>(){
        protected synchronized DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static Date convertStringToDate(String dateStr) throws ParseException {
        Date date = DATE_FORMAT_THREAD_LOCAL.get().parse(dateStr);
        return date;
    }

    public static void main(String[] args) throws ParseException {
        Date date = convertStringToDate("2019-10-23 10:26:00");
        System.out.println(date);
    }
}

猜你喜欢

转载自blog.csdn.net/zhaoqingquanajax/article/details/112382792