Java中的ThreadLocal类

时间:2020-01-09 10:35:08  来源:igfitidea点击:

在多线程环境中,共享对象将需要同步以避免通过并发访问而损坏,但是同步非常昂贵。另一种选择是给每个线程自己的实例,并避免数据共享。这就是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实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。