1. ThreadLocal 是如何保证线程安全的?
简单来说,造成线程不安全的原因是多个线程同时去更新共享数据,处理共享数据常用的方法就是加锁,通过加锁的方式来控制线程对共享数据的访问,例如乐观锁和悲观锁。
ThreadLocal 保证线程安全的方式是为每个线程提供一个独立的变量副本来解决冲突问题,每个线程更改的都是自己的变量副本,从根本上解决解决问题。
2. ThreadLocal 使用场景
ThreadLocal 对于每个线程都需要拥有自己的一份本地变量副本的情况下是最为适合的,常用于 Session 会话或数据库连接等。
阿里编码规约上亦有通过 ThreadLocal 保证线程安全性的案例,在第一章第六节并发安全处理部分,用于 SimpleDateFormat 做格式化处理部分。
3. ThreadLocal 简单使用
public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new RunnableImpl()).start();
}
}
static class RunnableImpl implements Runnable {
@Override
public void run() {
try {
// 1、获取随机数
int random = (int) Math.random() * 1000;
// 2、设置初始值
threadLocal.set(random);
// 3、等待其他线程进行更新
Thread.sleep(3000);
// 4、获取初始值
System.out.println("存取内容是否一致:" + (random == threadLocal.get()));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 不需要用到数据时进行手动删除,避免引起内存泄漏
threadLocal.remove();
}
}
}
}
运行结果:
4. ThreadLocal 解析
// 常用于创建 ThreadLocal 对象同时设置初始值 public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } // 设置初始值为0,默认为 null private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
// 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); }
public T get() { // 获取当前线程并拿到当前线程的 ThreadLocalMap 引用 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 若引用不为 null 则代表已实例化,可以获取内部属性 if (map != null) { // 获取内部类实例,Entry为ThreadLocalMap的内部类 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { // 获取Entry对象的value属性,value为set方法设置的值 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 进行初始化 return setInitialValue(); }
/** * 当不需要对象副本时请执行remove操作,避免内存溢出 * static class Entry extends WeakReference<ThreadLocal<?>> * ThreadLocalMap.Entry 实例 key 为弱引用,垃圾回收时无论内存是否充足都会被回收 * 此时 value 作为强引用未能进行回收但已无法通过 ThreadLocal 获取该数据 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }