线程安全杂谈之ThreadLocal

前言

保证线程安全的一个方法是线程封闭,而ThreadLocal关键字就能实现这一点,用ThreadLocal实现的变量会和线程绑定,每个线程对应自己的一份数据,自然就产生不了竞争资源问题。

使用方式

public class ThreadLocalTest {
    public static  ThreadLocal<String> itl = new InheritableThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return new Date().toString();
        }
        @Override
        protected String childValue(String parentValue) {
            return parentValue + " hello";
        }
    };
    public static  ThreadLocal<String> it2 = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return new Date().toString();
        }

    };


    public static void main(String[] args) throws InterruptedException {
        itl.get();//1
        for(int i =0;i<5;i++) {//2
            Thread.sleep(1000);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " : " + itl.get());
                }
            }).start();
        }
        for(int i =0;i<5;i++) {//3
            Thread.sleep(1000);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " : " + it2.get());
                }
            }).start();
        }
    }
}

运行结果

Thread-0 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-1 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-2 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-3 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-4 : Tue Apr 18 00:23:17 CST 2017 hello
Thread-5 : Tue Apr 18 00:23:23 CST 2017
Thread-6 : Tue Apr 18 00:23:24 CST 2017
Thread-7 : Tue Apr 18 00:23:25 CST 2017
Thread-8 : Tue Apr 18 00:23:26 CST 2017
Thread-9 : Tue Apr 18 00:23:27 CST 2017

代码讲解

这里面有2个类,ThreadLocal和InheritableThreadLocal,从字面意思上就能看出来第二个类具有继承的功能。
这边我在threadlocal里面初始化了一个date,如果和线程相关,每次输出的时间应该是不同的。
在代码块3,我开了5个线程去输出it2的值,因为it2是线程相关的,所以输出的5个值,应该是不同的。看后面5个输出。
而InheritableThreadLocal是线程继承的,在代码块1,也就是main线程,我把it1初始化了,而在之后5个子线程中,会继承的使用父线程的值,所以从输出可以看到,5个输出都是一样的。没有因为sleep而改变。
如果把代码1注释掉呢,大家可以试下,就能领略到这个特性。

源码讲解

ThreadLocal是怎么和线程绑定的

首先我们可以在ThreadLocal的源码内看到2个Map

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

使用了ThreadLocal或InheritableThreadLocal包装的数据会分别保存到Thread内部的2个map中去,map的key是当前的threadlocal对象。
保存的动作以及map的初始化在调用get方法中实现

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
 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;
    }

很明显的看到,会在当前线程的map中去拿value,如果不存在,执行创建map,已经调用我们自己实现的initialValue方法,插入map并且返回。这边也有个乐观锁的判断。
InheritableThreadLocal类似,只不过他重载了getMap和createMap2个方法,设置到可继承的map中去

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

InheritableThreadLocal是怎么实现让子线程继承

做到这个机制,我们要去看线程的构造函数

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        //设置当前线程为父线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        //如果父线程存在inheritableThreadLocals复制到自己的Thread对象内
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

因此如果父线程初始化了inheritableThreadLocals ,子线程可以用同一个InheritableThreadLocal作为key去拿到value

ThreadLocal内部Map机制

作为一个map,肯定有entry,ThreadLocal的entry有点不同

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

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

弱引用,因此,当这个对象没被引用的时候,会被垃圾回收
从entry的内部结构也可以看出来,与传统的hashmap不同,这个内部map解决冲突的方式,并不是使用链表,而是直接循环放到下一个桶

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
                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被回收了,会把后面的数据往上推,保持冲突的key的数据连续
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

spring中ThreadLocal运用

正在努力攻读spring源码,下面这2块是我想解决的疑惑
- springmvc的请求都是基于线程的
- spring的事务嵌套

欢迎大神斧正或者补充

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值