拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO

ThreadLocal

白鹭 - 2022-02-11 1940 0 0

ThreadLocal 与 Thread 同步机制的比较

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

定义ThreadLocal的同时为当前执行绪的区域变量副本赋初始值

? 方式1:ThreadLocal#withInitial(Supplier<? extends S> supplier) withInitial的自变量是函式式界面Supplier<T> 
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进去,

 

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *