并发编程 -- ThreadLocal

一、ThreadLocal是什么?

并发情况下,一般可以采用synchronized关键字来保证共享变量的线程安全性。ThreadLocal在解决线程安全问题上提供了一种新的思路,即ThreadLocal为每一个线程提供了一个独立的变量副本(每个Thread中会都有一个ThreadLocal.ThreadLocalMap的成员属性来存放线程变量副本),来避免多个线程对共享变量的访问冲突问题。在某些情况下,ThreadLocal在解决线程安全问题上更加灵活、方便。
如果我们创建了一个ThreadLocal变量,那么访问该变量的每一个线程都会在自己的工作内存中创建一个该变量的副本。那么当多个线程同时操作这个变量时,实际上是在操作自己工作内存中的变量,从而避免了线程安全问题。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

二、ThreadLocal简单示例

public class Demo {
    
    
    //创建一个存储Stirng类型变量的ThreadLocal
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
    
    

        Thread thread1 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                threadLocal.set("a");
                System.out.println("thread1 local:" + threadLocal.get());

            }
        });

        Thread thread2 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                threadLocal.set("b");
                System.out.println("thread2 local:" + threadLocal.get());
            }
        });

        thread1.start();
        thread2.start();

        //设置主线程的ThreadLocal
        threadLocal.set("main");
        //打印主线程的ThreadLocal变量
        System.out.println("main local:" + threadLocal.get());
    }
}

可以看出,各线程内的ThreadLocal不会互相干扰,每个线程只能访问自己独有的ThreadLocal变量。可以看出,ThreadLocal另辟蹊径,在解决多线程同步问题上提供了一种新的思路。

三、ThreadLocal内部实现原理

1、ThreadLocal的set()方法

public void set(T value) {
    
    
	// 获取当前线程
    Thread t = Thread.currentThread();
    // 返回当前线程的threadlocals
    ThreadLocalMap map = getMap(t);
    if (map != null)
    	// 将(k:当前ThreadLocal实例,v:共享变量)放入进map中
        map.set(this, value);
    else
    	// 如果map为空,则创建该线程的ThreadLocalMap,并将(k,v)放入进map中
        createMap(t, value);
}

2、ThreadLocal的get()方法

public T get() {
    
    
	// 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    
    
    	// 如果map不为空,则获取键为该ThreadLocal对象的entry实例
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
    
    
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

3、ThreadLocal的remove()方法

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

总结:在每一个线程内部,都有一个ThreadLocalMap类型的threadLocals变量,用来存储线程变量副本。其中ThreadLocalMap是一个定制化的Hashmap,其Entry的构造方式、hash冲突解决方式与HashMap都不同。ThreadLocalMap中的key为ThreadLocal实例,value为线程变量副本。如果线程不消亡,该变量副本就会一直存在,有可能造成内存泄漏,因此在使用完毕后,要手动调用ThreadLocal中的remove()方法删除该线程下的ThreadLocals的变量副本。

四、ThreadLocalMap

ThreadLocalMap是ThreadLocal的一个静态内部类,他没有实现Map等任何集合的顶层接口,而是自己实现了一套独立的map功能,内部Entry对象也是独立实现的。

2.1 重要属性

/**
 * 初始容量 -- 必须是2的倍数
 */
private static final int INITIAL_CAPACITY = 16;

/**
 * 内部的Entry数组
 */
private Entry[] table;

/**
 * 用于统计元素数量
 */
private int size = 0;

/**
 * 扩容阈值
 */
private int threshold; // Default to 0

2.2 Entry

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

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

2.3 构造函数

初始化table数组默认容量为16、根据当前ThreadLocal对象经过哈希运算计算出数组中存储的下标、再实例化Entry对象并存储到table数组中、更改数量size与阈值threshold。其实从这里可以得出结论一个ThreadLocal实例只能操作存储一个线程局部变量,重复操作会覆盖元素

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

Guess you like

Origin blog.csdn.net/m0_46218511/article/details/116917034