ThreadLocal示例及源码浅析

实现数据隔离

了解一个东西,我们当然要先问为什么要了解他。

在多线程的访问环境下,我们都会考虑线程安全的问题,所谓线程安全,就是为了确保多个线程访问的资源能与在单线程访问环境下返回的保持结果一致,不会产生二义性,也可以说是保证多线程安全访问竞争资源的一种手段。

synchronized关键字和Java5新增的java.util.concurrent.locks下的Lock和ReentrantLock包从处理机制上来说,是同一类办法。即通过对对象或者代码块加锁的方式,保证每个线程在访问上锁资源时都必须先获得对象的锁,然后才能对资源进行访问,其他线程在未获得对象资源的锁之前,只能阻塞等待。

而ThreadLocal则另辟蹊径,它不强制性地让资源同时只能被一个线程访问,而是允许多个线程并发访问资源,为了保证共享资源的数据隔离性和一致性,通过为每个线程绑定一个共享资源的数据副本,让公有资源复制分发到每个线程实现私有化,每个线程用一个数据副本大家各用各的,互不相干,从而实现线程安全。

示例

代码

package threadLocal;

public class Test {
    //定义一个存储线程id的threadLocal副本
    ThreadLocal threadId = new ThreadLocal();
    //定义一个存储线程name的threadLocal副本
    ThreadLocal threadName = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
        //main线程
        test.threadId.set(Thread.currentThread().getId());
        test.threadName.set(Thread.currentThread().getName());

        System.out.println("main线程的id:" + test.threadId.get());
        System.out.println("main线程的Name:" + test.threadName.get());

        //一个新的线程
        Thread anotherThread = new Thread(){
            public void run(){
                test.threadId.set(Thread.currentThread().getId());
                test.threadName.set(Thread.currentThread().getName());

                System.out.println("another线程的id:"+ test.threadId.get());
                System.out.println("another线程的Name:" + test.threadName.get());
            }
        };

        anotherThread.start();
        //主线程main调用another线程的join()方法,就要等待another线程执行完毕,主线程才会继续往下执行
        anotherThread.join();

        System.out.println("main线程的id:" + test.threadId.get());
        System.out.println("main线程的Name:" + test.threadName.get());
    }
}

运行结果
ThreadLocal示例及源码浅析_第1张图片

我们在类中声明了两个ThreadLocal对象,一个对象用来存放当前执行线程的id,另一个对象用来存放当前执行线程的Name。执行后可以看出,两个线程,主线程main和我们新建的线程anotherThread线程操作的虽然是用一个test对象下的ThreadLocal对象,但是他们各自的属性数据都是隔离的,分别记录了自己的值,取出后也都保持了数据的隔离性。

总的来说,每个线程都会在内部维护的这个ThreadLocalMap可以看做一个Map,每个ThreadLocal都是这个Map中键值对的Key,通过Key值就可以对数据进行操作,而Value就是我们针对每个ThreadLocal对象set的那个值,正如上例中的:

test.threadId.set(Thread.currentThread().getId());
test.threadName.set(Thread.currentThread().getName());

为什么我会做出这样的类比,下面就带大家看一看ThreadLocal的源码。

ThreadLocal源码

看了ThreadLocal的一个简单应用,我们接下来可以看看ThreadLocal具体的实现过程。

public class ThreadLocal

从ThreadLocal类的定义可以看到ThreadLocal的泛型声明,因此理论上ThreadLocal中是可以存放任何类型的数据资源的。

ThreadLocal示例及源码浅析_第2张图片

上图中红框内的三个方法是ThreadLocal的核心方法,很好记,无非是存储数据,取出数据和移除数据。

get()

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

/**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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;
    }

set()

/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

而set则是常规设置,获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,则设置value值,否则就新建一个ThreadLocalMap用于存放value。

remove()

/**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * initialValue method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

Remove就更加简单了,同样是获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,则移除ThreadLocalMap中存放的值。

至此可以看出这些方法都是围绕着ThreadLocalMap 在操作,那么ThreadLocalMap 是一个什么东西。

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

每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。我们在实际操作中就是针对每个线程中声明的若干个ThreadLocal对象进行数据副本的操作。之前说过,每个ThreadLocal都是这个Map中键值对的Key,通过key值就可以对数据进行操作,这一点就体现在ThreadLocalMap 的设置上。

总结

在实际应用中,当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。比如jdbc的Connection属性,这个连接属性是每个数据访问线程都需要使用到的,并且各自使用各自的,所以需要通过数据副本的形式来保证线程间访问互不干扰。

相较于加锁机制实现线程安全,ThreadLocal是非阻塞的,当在性能上有特殊的要求是,我们可以优先考虑采用ThreadLocal来解决线程安全的问题。

你可能感兴趣的:(多线程)