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实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。

