天天看点

TreadLocal 详解

文章目录

    • 常见使用场景
      • 简单例子
      • 初始化值
        • 重写initial方法
        • 提供一个Supplier 实现
        • 延迟设置
      • 线程池或者ExecutorService中使用ThreadLocal
      • 最后再来一个例子
      • InheritableThreadLocal

ThreadLocal与同步机制都能保证线程安全,但是其使用的方法是不同的,ThreadLocal采用多线程多副本,不同线程之间互不影响。同步机制保证同一时候只能有一个线程访问共享资源。

也就是说ThreadLocal只能被同一个线程访问。不同线程即使是访问通一个ThreadLocal变量,线程之间的ThreadLocal变量也是不可见的

常见使用场景

简单例子

public class GreySwitchContext implements AutoCloseable{
	// 创建一个泛型的ThreadLocal
    public static final ThreadLocal<Boolean> ctx = new ThreadLocal<>();
    public static final boolean getGreySwitch() {
    	// 获取ThreadLocal
        return ctx.get();
    }
    public static final void setGreySwitch(boolean greySwitch) {
    	// 设置ThreadLocal
        ctx.set(greySwitch);
    }
    @Override
    public void close() throws Exception {
    	// 清除ThreadLocal 的值
        ctx.remove();
    }
}
           

初始化值

指定初始值的几种方法:

  • 重写 initialValue
  • 提供一个Suppplier实现
  • 延迟设置

重写initial方法

最简单的方法就是创建一个ThreadLocal的匿名子类,然后重写他的initialValue方法

public static final ThreadLocal<Boolean> ctx = new ThreadLocal<Boolean>(){
        @Override
        protected Boolean initialValue() {
            return Boolean.TRUE;
        }
    };
           

注意:如果默认值是一个随机数的话,每个线程看到的初始值是不一样的,适用于某些场景,比如初始值是当前时间。

提供一个Supplier 实现

第二个实现初始化值的方法是用它的静态工厂方法 withInitial(Supplier) , 传一个Supplier的接口实现作为参数。

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
        @Override
        public String get() {
            return String.valueOf(System.currentTimeMillis());
        }
    });
           

Supplier是一个函数接口,所有可以用Lambda表达式

ThreadLocal<String> threadLocal1 = ThreadLocal.withInitial(
            ()->{return String.valueOf(System.currentTimeMillis());}
    );
           

看起来比之前的更短一些了,其实还可以更短

ThreadLocal<String> threadLocal2 = ThreadLocal.withInitial(
            ()-> String.valueOf(System.currentTimeMillis()));
           

延迟设置

某些场景初始化值可能会依赖其他配置信息,导致你在创建ThreadLocal的时候无法给默认值,这种场景下就要用到延迟设置了下面是一个例子:

public class MyDateFormatter {
    private ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>();

    public String format(Date date) {
        SimpleDateFormat simpleDateFormat = getThreadLocalSimpleDateFormat();
        return simpleDateFormat.format(date);
    }
    private SimpleDateFormat getThreadLocalSimpleDateFormat() {
        SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
        if(simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            simpleDateFormatThreadLocal.set(simpleDateFormat);
        }
        return simpleDateFormat;
    }
}
           

上面这个例子,我们看到format方法调用getThreadLocalSimpleDateFormat方法是怎么获取一个SimpleDateFormat对象的。如果ThreadLocal里的SimpleDateFormat 是空的话就创建一个。一个线程就使用一个SimpleDateFormat对象。我们知道 SimpleDateFormat这个类是线程不安全的,所以多线程是不能使用它的,利用ThreadLocal完美解决这个问题。

线程池或者ExecutorService中使用ThreadLocal

如果你打算在ExecutorService或者线程池中使用ThreadLocal那么是不保证哪个线程会执行到哪个任务的,是随机的。所以你想每个线程都有他自己的对象,那么用ThreadLocal是very nice的。

最后再来一个例子

public class ThreadLocalExample {

    public static void main(String[] args) {
        MyRunnable sharedRunnableInstance = new MyRunnable();

        Thread thread1 = new Thread(sharedRunnableInstance);
        Thread thread2 = new Thread(sharedRunnableInstance);

        thread1.start();
        thread2.start();

        thread1.join(); //wait for thread 1 to terminate
        thread2.join(); //wait for thread 2 to terminate
    }

}
public class MyRunnable implements Runnable {

    private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

    @Override
    public void run() {
        threadLocal.set( (int) (Math.random() * 100D) );

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }

        System.out.println(threadLocal.get());
    }
}
           

创建了同一个MyRunnable对象sharedRunnableInstance 然后传给两个不同的线程。如果这里不是threadLocal,且set方法是synchronized修饰的,那么第二个线程的值会把第一个线程的值覆盖

但是threadLocal使两个线程之间的值不可见所以不会被覆盖。

InheritableThreadLocal

ThreadLocal的一个子类