• ThreadLocal小解


    ThreadLocal 与 Thread 同步机制的比较

    • Thread同步机制采用了以时间换空间方式,通过对象锁保证在同一个时间,对于同一个实例对象,只有一个线程访问。
    • ThreadLocal 采用以空间换时间方式,为每一个线程都提供一份变量,各线程间同时访问互不影响。
     

    定义ThreadLocal的同时为当前线程的局部变量副本赋初始值

    ✅ 方式1:ThreadLocal#withInitial(Supplier<? extends S> supplier)
    withInitial的参数是函数式接口Supplier<T> ,通过调用其get方法得到结果(Supplier<T>, in/since jdk1.8, a functional interface whose functional method is {@link #get()},represents a supplier of results.)。
    private static ThreadLocal<StringBuilder> threadLocal = ThreadLocal.withInitial(StringBuilder::new);

    ✅ 方式2:覆写protected方法initialValue(): 

    private static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<StringBuilder>() {
        @Override
        protected StringBuilder initialValue() {
            return new StringBuilder();
        }
    };
     

    ThreadLocal无法解决共享对象的更新问题

    对于如下代码。 threadLocal里操作的StringBuilder与全局StringBuilder是同一个内存对象。 所以,在多线程往自己的ThreadLocal里的StringBuilder里append数据的时候,操作的都是全局的StringBuilder。 所以,这段代码定义的ThreadLocal<StringBuilder>没有任何意义。  因为StringBuilder是线程不安全的,所以,会出现线程不安全问题:某些并发场景下会出现有的线程没有append进去。          如果想实现线程安全,那么,不是用ThreadLocal,而是用线程安全的StringBuffer,或者借助线程同步锁。
    static StringBuilder sb = new StringBuilder("init");
    private static ThreadLocal<StringBuilder> threadLocal = ThreadLocal.withInitial(() -> sb);
     
    所以说,最好不要用ThreadLocal来操作共享对象。 尽量仅让其持有当前线程里的对象。
     

    完整示例代码 of ThreadLocal无法解决共享对象的更新问题

    package jstudy.threadlocal;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.RandomUtils;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * {@link java.lang.ThreadLocal}无法解决共享对象的更新问题。本代码实例将证明这一点。
     * 结果虽然都append了,但是,是无序的
     * 所以,使用某个引用来操作共享对象时,依然需要进行线程同步
     */
    @Slf4j
    public class InitValueInThreadLocal {
        static StringBuilder sb = new StringBuilder("init");
        private static ThreadLocal<StringBuilder> threadLocal = ThreadLocal.withInitial(StringBuilder::new);
    
        static AtomicInteger integer = new AtomicInteger();
    
        public void print() {
            StringBuilder stringBuilder = threadLocal.get();
            int j = integer.getAndIncrement();
            log.info("初始:{}  val={}", stringBuilder.toString(), j);
    
            try {
                Thread.sleep(RandomUtils.nextInt(0, 5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stringBuilder.append("-" + j);
    
            threadLocal.remove();
            log.info(stringBuilder.toString());
        }
    
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 5; i++) {
                InitValueInThreadLocal1 ins = new InitValueInThreadLocal1();
                new Thread(() -> ins.print()).start();
            }
            TimeUnit.SECONDS.sleep(2);
            log.info(sb.toString());
        }
    }

    由于StringBuilder线程不安全,如下是 意料之外的结果。main方法里启动了5个线程,所以,本应该把0、1、2、3、4这5个数字append到sb对象里,却发现0没有append进去。

  • 相关阅读:
    stylus使用文档总结:选择器+变量+插值+运算符+混合书写+方法
    vue项目配置使用flow类型检查
    项目中存在的问题
    坚持
    Enum枚举类使用集合
    JS 随机数字抽签
    【转】Basic C# OOP Concept
    EF异常:WebForm、Console、Winform层不引入EF报错
    EF异常:“System.InvalidOperationException”类型的未经处理的异常在 mscorlib.dll 中发生
    WebServices中使用Session
  • 原文地址:https://www.cnblogs.com/buguge/p/15824081.html
Copyright © 2020-2023  润新知