ThreadLocal是什么

源码中的注释如下:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal<Integer> threadId =
 *         new ThreadLocal<Integer>() {
 *             @Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是 private static类型的,用于关联线程和线程的上下文。

按照注释的意思,可知ThreadLocal主要有两个作用

  1. 在一个线程内使用。可以避免将一个变量多次传递,所有使用该变量的地方都直接从当前的线程Thread中去获取。
  2. 当多个线程需要并发访问一个变量时,可以使用ThreadLocal来封装该对象,这使得每一个调用ThreadLocal对象的get()方法的线程,都会持有一个数据拷贝,每个线程访问自己内部的该数据拷贝。

ThreadLocal怎么用

ThreadLocal的方法如下图:

ThreadLocal

我们会使用到set、get、initialValue、remove等方法来操作ThreadLocal对象,其实都是操作ThreadLocal的内部类ThreadLocalMap对象。

Thread类,内部维护了两个变量,threadLocals和inheritableThreadLocals,它们的默认值是null,它们的类型是 ThreadLocal.ThreadLocalMap,也就是ThreadLocal类的一个静态内部类ThreadLocalMap。

在多个线程内保存一个数据副本的场景

public class ThreadLocalUsage04 {

    public static ExecutorService THREAD_POOL = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            THREAD_POOL.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalUsage04().date(finalI);
                    System.out.println(date);
                }
            });
        }
        THREAD_POOL.shutdown();
    }

    private String date(int seconds) {
        // 参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
        SimpleDateFormat simpleDateFormat = ThreadSafeDateFormatter.dateFormatThreadLocal.get();
        return simpleDateFormat.format(date);
    }

}

class ThreadSafeDateFormatter {

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

}

上面的代码使用到了ThreadLocal,将SimpleDateFormat对象用ThreadLocal包装了一层,使得多个线程内部都有一个SimpleDateFormat对象副本,每个线程使用自己的SimpleDateFormat,这样就不会产生线程安全问题了。

那么以上介绍的是ThreadLocal的第一大场景的使用,也就是利用到了ThreadLocal的 initialValue()方法,使得每个线程内都具备了一个SimpleDateFormat副本。

在一个线程内减少方法间变量传递的场景

public class ThreadLocalUsage05 {

    public static void main(String[] args) {
        init();
        new NameService().getName();
        new SexService().getSex();
        new ScoreService().getScore();
    }

    private static void init() {
        Student student = new Student();
        student.name = "Lemon";
        student.sex = "female";
        student.score = "100";
        ThreadLocalProcessor.studentThreadLocal.set(student);
    }

}

class ThreadLocalProcessor {

    public static ThreadLocal<Student> studentThreadLocal = new ThreadLocal<>();

}

class Student {

    /**
     * 姓名、性别、成绩
     */
    String name;
    String sex;
    String score;

}

class NameService {

    public void getName() {
        System.out.println(ThreadLocalProcessor.studentThreadLocal.get().name);
    }

}

class SexService {

    public void getSex() {
        System.out.println(ThreadLocalProcessor.studentThreadLocal.get().sex);
    }

}

class ScoreService {

    public void getScore() {
        System.out.println(ThreadLocalProcessor.studentThreadLocal.get().score);
    }

}

通过将Student对象存放到ThreadLocal类型的studentThreadLocal对象中,后续该线程内部需要获取值时,可以直接从ThreadLocal中取,避免了将Student对象在多个方法直接传递的问题。

ThreadLocal的源码解析

Thread、ThreadLocalMap以及ThreadLocal三者之间的包含关系

如上图所示,我们可以在主线程中创建多个ThreadLocal对象,来包裹目标数据,然后只要有一个线程访问了这些ThreadLocal对象,那么在这个线程内部就会创建一个 ThreadLocalMap对象,并以键值对的形式存储到 ThreadLocalMap对象中,ThreadLocalMap是一个Entry数组。

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

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

在前面的介绍中,已经说过,我们是通过操作ThreadLocal对象的API(set、get、remove)来控制ThreadLocalMap对象的。

当一个线程调用ThreadLocal的set方法设置变量的时候,当前线程的ThreadLocalMap就会存放一个记录,这个记录的键为ThreadLocal的弱引用,value就是通过set设置的值,这个value值被强引用。

ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLocal依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的值不会被回收,这个时候Map中就可能存在key为null但是值不为null的项,所以在使用ThreadLocal的时候要养成及时remove的习惯。

ThreadLocalRandom

避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降,JDK7之后,可以使用ThreadLocalRandom来获取随机数

解释一下竞争同一个seed导致性能下降的原因,比如,看一下Random类的 nextInt()方法实现:

public int nextInt() {
    return next(32);
}

调用了next(int bits)方法,这是一个受保护的方法:

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

而这边的seed是一个全局变量:

/**
 * The internal state associated with this pseudorandom number generator.
 * (The specs for the methods in this class describe the ongoing
 * computation of this value.)
 */
private final AtomicLong seed;

多个线程同时获取随机数的时候,会竞争同一个seed,导致了效率的降低。

总结

ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

参考:

深入理解ThreadLocal:拨开迷雾,探究本质

如果觉得我的文章对你有用,请随意赞赏