Java ThreadLocal类解析

ThreadLocal类

用于为线程提供一份本地变量,仅限某一线程自身使用,用这种牺牲空间的方式换取了并发的效率。

但ThreadLocal并不是设计来解决并发安全问题的方法,每条线程所使用的都是独立的副本,ThreadLocal实例在每条线程中的表现可能都不同,也正因为这个特性,可以被用于设计web应用中session,每个用户会话连接web容器会为其分配一条线程,每条线程中的session也不相同。

验证ThreadLocal独立于每条线程的特性:

package test;

import java.util.HashMap;
import java.util.concurrent.*;

public class Test implements Callable<String>{
    private static final ThreadLocal<String> nameLocal = new ThreadLocal<>();
    private static final HashMap<String ,Integer> hashMap = new HashMap<>();

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Test t = new Test();
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> futureTask = executorService.submit(t);
        Future<String> futureTask1 = executorService.submit(t);
        System.out.println(futureTask.get());
        System.out.println(futureTask1.get());
    }

    @Override
    public String call() throws Exception {
        if(nameLocal.get() == null){
            int id = (int)(Math.random()*1000);
            nameLocal.set("id"+id);
        }
        if(hashMap.size() == 0){
            hashMap.put("key",(int)(Math.random()*1000));
        }
        Integer value = hashMap.get("key");
        return nameLocal.get()+"   "+value;
    }
}

结果:

从结果看,nameLocal显然在不同线程中分别被创建了一次,每条线程运行的时候为其分配了一个初始化的空nameLocal,而同样声明为static final的hashMap一共被创建了一次,最终在两条线程中是完全相同的hashMap。以上代码重复运行多次总有上述现象,也排除了一些运行时的偶然因素。

从源码上看:

ThreadLocal类set源码:

	//ThreadLocal   set
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

Thread类成员变量threadLocals声明:

	//Thread成员变量threadLocals声明
	    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal的内部类ThreadLocalMap中的set源码:

	//ThreadLocalMap  set
	 /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be 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();
        }

从上边的源码可以看出,set方法将变量放进了名为ThreadLocalMap类(实际上该类是ThreadLocal的内部类)的实例中,而该内部类的实例又是从Thread类实例的成员变量拿到的。可以认为,ThreadLocal之所以能保证对每条线程独立的原因之一,就是用于储存数据的ThreadLocalMap对每条线程保持了独立。

综述:

ThreadLocal用来隔离数据,通过防止多个线程对同一数据进行读写提高了效率,也方便了在同一线程中的传参。但是和synchronized的功能有本质上的区别


猜你喜欢

转载自blog.csdn.net/my_dearest_/article/details/80146776