菜鸟看完就懂的ThreadLocal

Java ThreadLocal类允许创建只能由同一线程读写的变量。因此,即使两个线程正在执行相同的代码,并且该代码具有对相同ThreadLocal变量的引用,两个线程也无法看到彼此的ThreadLocal变量。因此,Java ThreadLocal类提供了一种使代码线程安全的简单方法。

 

 

创建ThreadLocal

 

 

你可以像创建其他Java对象一样,通过new运算符创建ThreadLocal实例。代码如下:

private ThreadLocal threadLocal = new ThreadLocal();

每个线程只需要执行一次。多个线程现在可以在这个ThreadLocal中获取和设置值,并且每个线程只能看到它自己设置的值。

 

设置ThreadLocal值

 

创建ThreadLocal之后,可以使用其set()方法设置要存储在其中的值。

threadLocal.set("A thread local value");

 

 

获取ThreadLocal中的值

 

你可以使用ThreadLocal的get()方法读取存储在ThreadLocal中的值。获取存储在Java ThreadLocal内部的值的示例代码如下:

String threadLocalValue = (String) threadLocal.get();

 

 

删除ThreadLocal中的值

 

可以删除ThreadLocal变量中设置的值。你可以通过调用ThreadLocal的remove()方法来删除值。代码如下:

threadLocal.remove();

 

 

泛型ThreadLocal

 

你可以使用泛型创建ThreadLocal。使用泛型,只能将泛型对象设置为ThreadLocal上的值,就不必强制转换get()返回的值。创建泛型ThreadLocal如下:

private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();

现在,你只能将字符串存储在ThreadLocal实例中。此外,你无需强制转换从ThreadLocal获得的值:

myThreadLocal.set("Hello ThreadLocal");String threadLocalValue = myThreadLocal.get();

 

初始化ThreadLocal值

 

在用新值调用set()之前,可以为Java ThreadLocal设置一个初始值,该值将在第一次调用get()时使用。可以通过两个选项为ThreadLocal指定初始值:

  • 创建一个覆盖InitialValue()方法的ThreadLocal子类。

  • 使用Supplier接口实现创建ThreadLocal。

下面我将为你讲解这两种方式。

 

覆盖InitialValue()

为Java ThreadLocal变量指定初始值的第一种方法是创建ThreadLocal的子类,该子类将覆盖其initialValue()方法。创建ThreadLocal子类的最简单方法是直接在创建ThreadLocal变量的位置创建一个匿名子类。下面是一个创建ThreadLocal的匿名子类的示例,该子类将覆盖initialValue()方法:

private ThreadLocal myThreadLocal = new ThreadLocal<String>() {    @Override protected String initialValue() {        return String.valueOf(System.currentTimeMillis());    }};

注意:不同的线程仍然会看到不同的初始值,每个线程都将创建自己的初始值。只有从initialValue()方法返回完全相同的对象,所有线程才会看到相同的对象。但是,一开始使用ThreadLocal的整个目的是避免不同的线程看到相同的实例。

 

提供Supplier实现

为Java ThreadLocal变量指定初始值的第二种方法是将其静态工厂方法与Initial(Supplier)一起使用,该方法将Supplier接口实现作为参数传递。此Supplier实现为ThreadLocal提供初始值。下面是一个使用其withInitial()静态工厂方法创建ThreadLocal的示例,并将简单的Supplier实现作为参数传递:

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

由于Supplier是函数式接口,因此可以使用Java Lambda表达式来实现。下面是将Supplier实现作为lambda表达式提供给withInitial()的样子:

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

如你所见,这比前面的示例要短一些。但是,使用最密集的lambda表达式语法,它甚至可以变得更短:

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

 

 

延迟设置ThreadLocal值

 

在某些情况下,不能使用设置初始值的标准方法。例如,也许你需要一些配置信息,这些信息在创建ThreadLocal变量时不可用。在这种情况下,可以延迟设置初始值。下面是一个如何在Java 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()方法来获取Java SimpleDatFormat实例。如果尚未在ThreadLocal中设置SimpleDateFormat实例,则将创建新的SimpleDateFormat并在ThreadLocal变量中进行设置。一旦线程在ThreadLocal变量中设置了自己的SimpleDateFormat,则该线程将使用相同的SimpleDateFormat对象。但仅适用于该线程。每个线程都创建自己的SimpleDateFormat实例,因为它们看不到在ThreadLocal变量上设置的其他实例。

 

SimpleDateFormat类不是线程安全的,因此多个线程不能同时使用它。为了解决此问题,上面的MyDateFormatter类为每个线程创建一个SimpleDateFormat,因此每个调用format()方法的线程都将使用其自己的SimpleDateFormat实例。

 

将ThreadLocal与线程池或ExecutorService一起使用

 

如果你打算在传递给Java线程池或Java ExecutorService的任务中使用Java ThreadLocal,请记住,你无法保证哪个线程将执行你的任务。但是,如果你只需要确保每个线程都使用自己的某个对象实例,那么这不是问题。然后,你可以将Java ThreadLocal与线程池或ExecutorService一起使用。

 

完整的ThreadLocal示例

下面是一个完全可运行的ThreadLocal代码实例:​​​​​​​

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实例,它被传递给两个不同的线程。两个线程都执行run()方法,因此在ThreadLocal实例上设置了不同的值。如果对set()调用的访问已同步,并且它不是ThreadLocal对象,则第二个线程将覆盖第一个线程设置的值。

 

但是,由于这是一个ThreadLocal对象,因此两个线程无法看到彼此的值。因此,它们设置并获得不同的值。

 

InheritableThreadLocal

 

InheritableThreadLocal类是ThreadLocal的子类。不是每个线程在ThreadLocal中都有自己的值,而是InheritableThreadLocal将对值的访问权授予线程和该线程创建的所有子线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值