ThreadLocal分析

ThreadLocal分析

ThreadLocal的特点是组合在了Thread内部,使得每个线程都有自己独有的数据,从而避免了线程安全问题
ThreadLocal主要体现在精妙的设计思想中。缺点不明显,使用弱引用作为手段缓解内存泄漏的发生是其中一个思想



基本使用

package threadlocaltest;

public class Main {

    // 全局静态对象,一直被强引用,内部的值永远不会被回收,除非线程被回收,该线程内部的ThreadLocal也会被回收
    private static final ThreadLocal<String> userLocal = new ThreadLocal<>();

    private static final ThreadLocal<String> domainLocal = new ThreadLocal<>();

    public String getCurrentUser() {
        return userLocal.get();
    }

    public void setCurrentUser(String str) {
        userLocal.set(str);
    }

    public String getDomain() {
        return domainLocal.get();
    }

    public void setDomain(String str) {
        domainLocal.set(str);
    }


    public static void main(String[] args) {
        // 获取数据
        System.out.println(userLocal.get());
        System.out.println(domainLocal.get());
    }
}

全局静态是最常用的方式,也有不是全局静态的,运行在方法里的


提示:以下是本篇文章正文内容,下面案例仅供参考

一、set方法分析

往ThreadLocalMap里面添加数据

    public void set(T value) {
    	// 获取当前线程里面的ThreadLocal
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	// this是ThreadLocal
            map.set(this, value);
        else
        	// 初始化
        	// t.threadLocals = new ThreadLocalMap(this, firstValue);
            createMap(t, value);
    }
    // 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);
     }

	// ThreadLocalMap中的set方法
	private void set(ThreadLocal<?> key, Object value) {
			// entry数组,保存具体数据
            Entry[] tab = table;
            int len = tab.length;
            // ThreadLocal的hashCode对数组长度 - 1 的值进行取余,获取到索引下标
            int i = key.threadLocalHashCode & (len-1);
			// 循环,如果i位置有值
            for (Entry e = tab[i];
                 e != null;
                 // i++
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
				// 地址值判断,新值覆盖旧值
                if (k == key) {
                    e.value = value;
                    return;
                }
				// key为null说明被回收了,创建一个entry覆盖掉
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			// 没有命中,创建一个entry放到i的位置
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // 长度达到三分之二时,扩容,新数组长度是原来的2倍
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

二、get方法分析

    public T get() {
    	// 获取当前线程里面的ThreadLocal
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	// key是Threadlocal
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 第一次get,将value初始化成null
        return setInitialValue();
    }
	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
	    	// 如果key不一致,key为null则清理,不为null则return tab[i++]
	        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)
	        	// key被回收,说明是无用数据,回收掉
	            expungeStaleEntry(i);
	        else
	            i = nextIndex(i, len);
	        e = tab[i];
	    }
	    return null;
	}

三、remove方法分析

public void remove() {
	// 当前线程的ThreadLocal
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
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;
        }
    }
}

四、如果要设计ThreadLocal,需要考虑什么问题?

4.1、考虑线程间数据隔离的问题

可以参考java对ThreadLocal的措施,集成在了Thread里

ThreadLocal.ThreadLocalMap threadLocals = null;

4.2、考虑防止内存泄漏的方式

1,什么时候会内存泄漏?
内存就那么大,装满了还往里面装
![](https://img-blog.csdnimg.cn/50ff194820674e19ae5efc87dcac1287.png

2,为了防止内存泄漏,我们可以采取一些措施将没用的数据及时回收掉
2.1、那么如何标识这些没用的数据呢?
首先要把没用的数据做一个标识,ThreadLocal中是将key作为了弱引用,这样就可以在外部没有强引用的干预下,直接干掉,就变成了null

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
    	// 弱引用的key
        super(k);
        value = v;
    }

2.2、标识好后什么时候回收?
容易想到的是,我们在每次put时检查一下,但是为了防止一直不put,我们在get时也检查一下有没有无用数据,更严谨一点,提供remove方法,用户想什么时候删除就什么时候删除,减少废弃数据在内存的停留时间(而不用等到get和set时才清理)


总结

在Threadlocal中,提供了expungeStaleEntry方法将无用的entry置空掉,从而防止内存泄漏

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;
    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

这个回收方法在get、set、remove、数组扩容时都被使用到
ThreadLocalMap的key使用弱引用本身就是优化内存泄漏的一种手段,在ThreadLocal不是全局静态使用方式时,弱引用会生效将key回收,表示这是一条被废弃的数据,但是value还被entry持有,entry被entry数组持有,所以在get或set时都会触发expungeStaleEntry的回收机制,但是为了减少无用数据在内存里的停留时间,可以调用remove方法加速回收
remove更偏向于一种开发规范,防止极端情况发生
如果是全局静态的ThreadLocal则永远不会回收,如果使用了tomcat线程池,则临时线程销毁时ThreadLocal也会跟随回收
核心线程则不会,下次在put数据时同样的ThreadLocal会覆盖掉旧的

ThreadLocalJava中的一个线程本地变量,它提供了一种简单的解决多线程并发访问共享变量的方案。每个ThreadLocal对象维护了一个独立的变量副本,每个线程都可以访问自己的变量副本,从而避免了多线程之间对同一变量的竞争。 ThreadLocal的原理可以简单概括为以下几个步骤: 1. 每个Thread对象内部都有一个ThreadLocalMap对象,ThreadLocalMap是ThreadLocal的核心数据结构,用于存储每个ThreadLocal对象对应的变量副本。 2. 在ThreadLocal对象中调用set()方法时,ThreadLocal首先获取当前线程的ThreadLocalMap对象,然后将当前ThreadLocal对象作为key,将要存储的变量副本作为value,存储到ThreadLocalMap中。 3. 在ThreadLocal对象中调用get()方法时,ThreadLocal首先获取当前线程的ThreadLocalMap对象,然后以当前ThreadLocal对象作为key获取对应的变量副本,从而实现了多线程之间的变量隔离。 4. 在ThreadLocal对象中调用remove()方法时,ThreadLocal首先获取当前线程的ThreadLocalMap对象,然后将当前ThreadLocal对象从ThreadLocalMap中删除。 需要注意的是,由于ThreadLocalMap是存储在线程中的,因此需要注意内存泄漏的问题。如果在ThreadLocal对象使用结束后没有调用remove()方法,会导致ThreadLocal对象一直存在于ThreadLocalMap中,从而引起内存泄漏。因此,在使用ThreadLocal对象时,需要注意及时调用remove()方法,以避免内存泄漏问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值