分析ThreadLocal如何做到单个线程独享

简介: 分析ThreadLocal如何做到单个线程独享

分析ThreadLocal如何做到单个线程独享

前情概要

  • 我们可能都知道SimpleDateFormat这个类的实例它不是线程安全的,如果不知道,我把代码贴这儿:
// 类的成员变量
  protected Calendar calendar;
  // 这个私有方法会对calendar对象进行赋值,但是没有加锁,在多并发场景下,就造成了问题。
  // Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);
        // ....其他代码
    }
public class ThreadLocalDemo06 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            final int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalDemo06().date(finalI);
                    System.out.println(Thread.currentThread().getName() + "  " + date);
                }
            });
        }
        threadPool.shutdown();
    }
    public String date(int seconds) {
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
        return dateFormat.format(date);
    }
}
class ThreadSafeFormatter {
    // public final static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal 是不是会更好呢!
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("mm:ss");
        }
    };
}
  • 这里使用了dateFormatThreadLocal.get()来保证获取到的一定是一个线程一个。

代码分析

java.lang.ThreadLocal#initialValue

  • 这是一个protected 修饰的方法,默认返回null。具体实现交给子类,返回类型T。
protected T initialValue() {    return null;}

java.lang.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() {
        // 1. 获取当前线程的引用
        Thread t = Thread.currentThread();
        // 2. 获取这个线程内部维护的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 3. 以当前的ThreadLocal对象作为key去获取键值对Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 4. 获取值
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
  • 从这个方法中可以看到,当已经存在对象时,获取对象分四步走。
  • 当这个对象还未存在时,调用setInitialValue()进行初始化。

java.lang.ThreadLocal#setInitialValue

/**
     * 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();
        // 1. 获取当前线程
        Thread t = Thread.currentThread();
        // 2. 获取线程中维护的ThreadLocalMap对象。
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 3. 将该线程中的map,以ThreadLoacal本身作为key,将Value进行设置。
            map.set(this, value);
        else
            // 如果map都不存在,则在该线程中创建该map,并进行set value。
            // 真是节省空间,都是用到才去创建!
            createMap(t, value);
        return value;
    }
  • 从这个方法看到,是这个方法去真正的调用了initialValue方法进行初始化的,所以ThreadLocal的对象获取其实也是一个懒加载的方式,用到了才去进行初始化。
  • 也看到了这里是对Thread中的map进行赋值的位置。

小结

  • 每个线程的default级别的成员变量ThreadLocal.ThreadLocalMap threadLocals都是由ThreadLocal进行维护的。

  • threadLocals 以ThreadLocal对象作为key,保证了一个线程中,同一个ThreadLocal的实例去执行get方法去获取到这个的对象都是唯一的。这也解释了为什么ThreadLocal的对象在示例中使用了static修饰,为了保证它的唯一性,当然,我更乐意在 static 前再加上 final 进行修饰。

Debug实操

  • 代码分析看过了,进行debug实际查看一下,就用以上的示例代码。
  • 示例代码中,线程池的核心线程数是16,最大线程数也是16.[暂且不论创建线程池的姿势不丝滑],那么在线程启动后,应当:每个线程在执行get()方法前,threadLocals这个map应该为null,执行后,每个线程保证只有一个。

初始化之前

  • 第一次执行get时,没有获取到需要进行setInitialValue()。可以观察到this对象的确不存在,他去进行set初始化。

初始化之后

  • 第二次这个线程再去获取时,就会去map中获取。

总结

要理清Thread、ThreadLocal、ThreadLocal.ThreadLocalMap三者的关系,结合debug,不难看到它究竟是怎么实现线程独享的一个ThreadLocal对象的。

目录
相关文章
|
27天前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
31 1
|
1月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
61 0
|
2月前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。
|
3月前
|
存储 监控 Java
|
3月前
|
安全 Java 开发者
Swing 的线程安全分析
【8月更文挑战第22天】
61 4
|
3月前
|
Java 数据库连接 数据库
当线程中发生异常时的情况分析
【8月更文挑战第22天】
100 4
|
3月前
|
安全 Java 程序员
线程安全与 Vector 类的分析
【8月更文挑战第22天】
50 4
|
3月前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
2月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
30 0
|
3月前
|
算法 安全 Java
深入解析Java多线程:源码级别的分析与实践
深入解析Java多线程:源码级别的分析与实践