• ThreadLocal从源码到应用


    最早接触到ThreadLocal是在阅读dianping的Cat-client,当时对它不是很理解,就搜索了一下,大概了解是一种解决线程安全问题的机制。现在再次阅读《实战java高并发程序设计》时,又重新对它有了更深一步的了解。

    并发程序很重要的主题就是解决多线程安全的问题,最常见的处理办法就是引入锁的机制;但是锁使得各个线程对临界区的使用效率变差,于是有了一种新的思路,即每个线程独立管理某个变量,变量的修改在线程中时独立的。就好比,以前锁的机制是100个人签到,只有一个签字薄;而现在ThreadLocal是每个人一张纸。

    不过上面的场景,只是threadLocal的一个应用场景。还有个例子,是在城市里面倒车。小明去上班要先做公交车在做地铁,如果每次坐车都买票,那么时间效率很差。于是小明办理了一张通用的公交卡,公交车和地铁都可以刷。而小蓝小红也有这样的公交卡,它们的公交卡彼此之间是独立的。这就是ThreadLocal的作用!

    所以说,ThreadLocal并不是解决线程共享问题,而是为了解决单个线程内部变量的独立性和参数传递的问题。

    那么它的原理时什么样的呢?

    说白了,就是每个线程自己有一个Map,这个Map采用了线性探测法来存储变量。接下来主要阅读下代码吧:

    public T get() {
        Thread t = Thread.currentThread();
        // 获取当前线程的localmap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 用当前的变量作为key查询对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果不存在的话,初始化变量
        return setInitialValue();
    }
    
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    主要时那个getEntry方法:

    private Entry getEntry(ThreadLocal<?> key) {
        // 通过当前key的hashcode与列表的长度做 &操作,判断存储的位置
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            // 如果不存在的话,进入getEntryAfterMiss方法
            // 这种情况,可能是key被回收掉了;也可能是hash冲突了
            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)
                        expungeStaleEntry(i);
                    else
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }        
    

    需要注意的是,ThreadLocal里面的内存结构是这样的:

    由于key是弱引用,因此在gc的时候会被回收掉。所以entry中会包含key为null的值,那么这里会不会有内存泄漏呢?可以看一下expungeStaleEntry方法,在发现有value为null的时候,threadlocal会自动扫描其他的元素,看看有没有key为null的,如果有的话,一并移除。

    如果为null,需要清理对应的value:

    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;
    }
    

    官方推荐使用private static修饰,跟Java大神聊了下,总结一下ThreadLocal为什么推荐这样使用:

    1. 推荐用private修饰,是不向外部其他的对象也能引用到,防止干扰垃圾回收
    2. 推荐使用static,我个人的理解是为了把对象存储到方法去(static修饰的变量会存储在方法区),这样虽然内部的Entry是弱引用,但由于变量在方法区,也不会在gc的时候被回收掉。<---个人理解哈,如有不对,还请指正

    应用

    在cat的代码中:

    public class DefaultMessageManager extends ContainerHolder implements MessageManager, Initializable, LogEnabled {
        // we don't use static modifier since MessageManager is configured as singleton
    	private ThreadLocal<Context> m_context = new ThreadLocal<Context>();
    
        // 每个线程拥有独立的上下文信息
        private Context getContext() {
    		if (Cat.isInitialized()) {
    			Context ctx = m_context.get();
    
    			if (ctx != null) {
    				return ctx;
    			} else {
    				if (m_domain != null) {
    					ctx = new Context(m_domain.getId(), m_hostName, m_domain.getIp());
    				} else {
    					ctx = new Context("Unknown", m_hostName, "");
    				}
    
    				m_context.set(ctx);
    				return ctx;
    			}
    		}
    
    		return null;
    	}
    
        @Override
    	public void end(Transaction transaction) {
    		Context ctx = getContext();
    
    		if (ctx != null && transaction.isStandalone()) {
    			if (ctx.end(this, transaction)) {
    				m_context.remove();
    			}
    		}
    	}
    
  • 相关阅读:
    Execl获取真实行数
    日期工具类
    Eclipse & IDEA快捷键对比大全
    【转】IDEA导入Eclipse项目 详细步骤(亲自验证导入成功)
    京东的四大集团
    马云演讲视频
    阿里系的一些重要网站
    阿里系的几个电商网站
    腾讯几款QQ软件
    电脑版微信使用说明
  • 原文地址:https://www.cnblogs.com/xing901022/p/7828764.html
Copyright © 2020-2023  润新知