ThreadLocal详解

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。ThreadLocal详解,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、概念

threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。这也是spring声明式事务的原理

在这里插入图片描述

代码如下:

public class ThreadLocalDemo1 {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
        }).start();



        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            threadLocal.set("test");

            System.out.println(Thread.currentThread().getName()+":插入数据");

            System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
        }).start();
    }
}

运行结果:由此可见多个线程之间数据是不共享的

Thread-1:插入数据
Thread-1:test
Thread-0:null

Thread线程可以拥有多个ThreadLocal来维护自己ThreadLocalMap中的数据(ThreadLocalMap中每条数据的key值都是一个ThreadLocal对象)

package com.example.demo.thread;

public class ThreadLocalDemo1 {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            threadLocal.set("test");
            threadLocal1.set("demo");

            System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
            System.out.println(Thread.currentThread().getName()+":"+threadLocal1.get());
        },"t1").start();
    }
}

执行结果:

t1:test
t1:demo

二、具体应用

  1. 每个线程需要有自己单独的实例
  2. 实例需要在多个方法中共享,但不希望被多线程共享

第一种很简单,可以参考上面代码。现以第二种为例,展示代码

public class ThreadLocalDemo {
    public static void main(String[] args) {
        User user = new User("jack");
        new Service1().service1(user);
    }

}

class Service1 {
    public void service1(User user){
        //给ThreadLocal赋值,后续的服务直接通过ThreadLocal获取就行了。
        UserContextHolder.holder.set(user);
        new Service2().service2();
    }
}

class Service2 {
    public void service2(){
        User user = UserContextHolder.holder.get();
        System.out.println("service2拿到的用户:"+user.name);
        new Service3().service3();
    }
}

class Service3 {
    public void service3(){
        User user = UserContextHolder.holder.get();
        System.out.println("service3拿到的用户:"+user.name);
        //在整个流程执行完毕后,一定要执行remove
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder {
    //创建ThreadLocal保存User对象
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}

class User {
    String name;
    public User(String name){
        this.name = name;
    }
}


执行结果:

service2拿到的用户:jack
service3拿到的用户:jack

三、ThreadLocal原理

1、ThreadLocal的set()方法

 public void set(T value) {
    //1、获取当前线程
    Thread t = Thread.currentThread();
    //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
    //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        // 初始化thradLocalMap 并赋值
        createMap(t, value);
}

每次在线程中threadLocal.set()时其实是往每个线程自己的map(ThreadLocalMap)中放入值,key是threadLocal、value是传入的对象。

2、map.set()

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    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)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

在map.set中其实是new出了一个Entry对象

3、Entry类

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

    
}

Entry类实际上继承了WeakReference(弱引用),在构造方法中调用WeakReference的构造方法,也就是每次new Entry的时候调new WeakReference(key)把key(key和tl指向的是同一个ThreadLocal对象)变成了弱引用(被WeakReference对象指向)。而之所以是弱引用的目的是防止内存泄露,因为tl和key都指向同一个ThreadLocal对象,若key是其他引用类型即使tl=null ThreadLocal对象依然不会被回收,因为有key指向而我们又没法操作key=null(我们拿不到key),这就造成ThreadLocal对象占用的内存永远不会被回收也就是内存泄露,而用弱引用,只要gc启动它便被回收不会内存泄露。但是当ThreadLocal被回收了,key就等于null了,那么与之对应的value则永远不会被操作到(map能指向value,但是我们操作不到了),所以value还是会导致内存泄露,所以每次用完tl必须tl.remove(在这个方法内部其实是调用ThreadLocalMap的remove方法,彻底干掉key、value键值对)

在这里插入图片描述

4、createMap()

//这个是threadlocal 的内部方法
void createMap(Thread t, T firstValue) {
   t.threadLocals = new ThreadLocalMap(this, firstValue);
}
 
 
//ThreadLocalMap 构造方法
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);
}

5、get()

public T get() {
    //1、获取当前线程
    Thread t = Thread.currentThread();
    //2、获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    //3、如果map数据为空,
    if (map != null) {
        //3.1、获取threalLocalMap中存储的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
    return 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;
}


protected T initialValue() {
    return null;
}

6、remove()

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

调用ThreadLocalMap的remove方法,彻底干掉key、value键值对,防止内存泄露

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/153406.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!