Java中的ThreadLocal类
在多线程环境中,共享对象将需要同步以避免通过并发访问而损坏,但是同步非常昂贵。另一种选择是给每个线程自己的实例,并避免数据共享。这就是Java中的ThreadLocal类。
Java中的ThreadLocal类提供线程局部变量,其中每个线程都有其自己的,独立初始化的变量副本。
如何创建和访问线程局部变量
使用ThreadLocal()构造函数,我们可以创建线程局部变量。例如,如果要创建一个线程局部变量,该局部变量存储单个线程的整数值。
private static final ThreadLocal<Integer> tcValue = new ThreadLocal<Integer>();
在此请注意,ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段。
要获取或者设置该线程局部变量的值,可以使用ThreadLocal类的get()和set()方法。
tcValue.set(1); Integer threadId = tcValue.get();
我们可以使用initialValue()方法为此线程局部变量返回当前线程的"初始值"。线程在第一次使用get()方法访问变量时将调用此方法。 ThreadLocal类中initialValue()的默认实现只是返回null。
如果希望线程局部变量具有非null的初始值,则需要将ThreadLocal子类化并重写initialValue()方法。
Java 8及更高版本的withInitial(Supplier <?extended S>供应商)方法也可以用于创建线程局部变量。由于此方法使用Supplier功能接口作为参数,因此可以使用lambda表达式来实现它。
这是一个代码片段,使用这些方法可以使它们更加清晰。
private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable with initialValue() implementation to //return initial value to each thread private static final ThreadLocal threadId = new ThreadLocal() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } };
如果使用withInitial()方法,则可以用以下代码替换initialValue()实现。
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(()-> {return nextId.getAndIncrement();});
Java ThreadLocal类示例
1要在每个线程(用户ID或者事务ID)关联状态的情况下使用ThreadLocal类。在这种情况下,我们可以为每个线程分配唯一值的线程局部变量。只是为了验证分配的ID是否以另一种方法再次显示。
import java.util.concurrent.atomic.AtomicInteger; class UniqueIdGenerator{ private static final AtomicInteger nextId = new AtomicInteger(0); // ThreadLocal variable private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // Returns the current thread's unique ID, assigning it if necessary public static int getId() { return threadId.get(); } } public class ThreadClassDemo implements Runnable{ @Override public void run() { System.out.println("Thread " + Thread.currentThread().getName() + " Value - " + UniqueIdGenerator.getId()); ThreadClassDemo td = new ThreadClassDemo(); // display stored Id again to verify td.displayThreadId(); } public void displayThreadId(){ System.out.println("Thread " + Thread.currentThread().getName() + " Stored Value - " + UniqueIdGenerator.getId()); } public static void main(String[] args) { //ThreadClassDemo td = new ThreadClassDemo(); Thread thread1 = new Thread(new ThreadClassDemo()); Thread thread2 = new Thread(new ThreadClassDemo()); Thread thread3 = new Thread(new ThreadClassDemo()); Thread thread4 = new Thread(new ThreadClassDemo()); Thread thread5 = new Thread(new ThreadClassDemo()); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); } }
输出:
Thread Thread-3 Value - 2 Thread Thread-0 Value - 0 Thread Thread-2 Value - 4 Thread Thread-4 Value - 3 Thread Thread-1 Value - 1 Thread Thread-1 Stored Value - 1 Thread Thread-4 Stored Value - 3 Thread Thread-2 Stored Value - 4 Thread Thread-0 Stored Value - 0 Thread Thread-3 Stored Value – 2
2我们也可以使用ThreadLocal作为同步代码的替代方法,因为同步的成本很高。在多线程环境中使用SimpleDateFormat时,我们确实需要对其进行同步,因为SimpleDateFormat的实例不是线程安全的。使用ThreadLocal可以为每个线程构造SimpleDateFormat实例。由于每个线程在该线程本地都有其自己的实例,因此没有机会受到另一个线程的干扰。
import java.text.SimpleDateFormat; import java.util.Date; class DateFormatInstance{ // ThreadLocal variable private static final ThreadLocal<SimpleDateFormat> threadLocalDateFmt = ThreadLocal.withInitial(()-> {return new SimpleDateFormat("dd/MM/yyyy");}); public static SimpleDateFormat getFormat() { return threadLocalDateFmt.get(); } } public class ThreadClassDemo implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + " Date formatter pattern is - " + DateFormatInstance.getFormat().toPattern()); System.out.println("Formatted date - " + DateFormatInstance.getFormat().format(new Date())); } public static void main(String[] args) { //ThreadClassDemo td = new ThreadClassDemo(); Thread thread1 = new Thread(new ThreadClassDemo()); Thread thread2 = new Thread(new ThreadClassDemo()); Thread thread3 = new Thread(new ThreadClassDemo()); thread1.start(); thread2.start(); thread3.start(); } }
输出:
Thread-1 Date formatter pattern is - dd/MM/yyyy Thread-2 Date formatter pattern is - dd/MM/yyyy Thread-0 Date formatter pattern is - dd/MM/yyyy Formatted date - 10/05/2018 Formatted date - 10/05/2018 Formatted date - 10/05/2018
有关Java中ThreadLocal的要点
- 线程局部变量是线程局部的。每个线程都有其自己的,独立初始化的变量副本。
- 每个线程都可以全局访问其自己的线程局部变量。如果线程正在调用多个方法,则可以在所有这些方法中访问线程局部变量。
- ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段
- 只要线程是活动的并且ThreadLocal实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。