Java ThreadLocal类相关方法调用示例

x33g5p2x  于2021-08-19 转载在 Java  
字(2.9k)|赞(0)|评价(0)|浏览(432)

Java ThreadLocal用于创建线程局部变量。我们知道,一个对象的所有线程都会共享其变量,所以该变量不是线程安全的。我们可以使用同步来实现线程安全,但如果我们想避免同步,我们可以使用ThreadLocal变量。
TheadLocal结构允许我们存储只有特定线程才能访问的数据。

ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户ID或事务ID)。

ThreadLocal类构造函数

  • ThreadLocal()- 创建一个线程局部变量。

ThreadLocal类方法

T get() - 返回该线程本地变量的当前线程副本中的值。
*受保护的T initialValue() - 返回当前线程的这个线程局部变量的 "初始值"。
void remove() - 移除当前线程对该线程局部变量的值。
void set(T value) - 将当前线程对该线程局部变量的拷贝设置为指定的值。

  • *static ThreadLocal withInitial(Supplier<? extends S> supplier) *- 创建一个线程局部变量。

ThreadLocal类实例

在这个例子中,下面的类生成了每个线程的本地唯一标识符。一个线程的ID在它第一次调用*ThreadId.get()*时被分配,并在后续调用中保持不变。

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalExample {
 // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    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 get() {
        return threadId.get();
    }
}

只要线程还活着,并且ThreadLocal实例可被访问,每个线程都持有对其线程-本地变量副本的隐式引用;在一个线程离开后,其所有线程-本地实例的副本都会被垃圾回收(除非存在对这些副本的其他引用)。

ThreadLocal现实世界的例子

在实际的Java应用中,我们使用ThreadLocal来存储登录用户Context实例。每个线程都会有自己的ThreadLocal实例。

在我们的例子中,我们为每个特定的userId建立了一个专用线程,这个线程是由我们创建的,所以我们可以完全控制它。
run()方法将获取用户上下文,并使用set()方法将其存储到ThreadLocal变量中。 

让我们首先创建一个包含userName的Context类。

Context.java

public class Context {
    private final String userName;

    Context(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "Context{" +
          "userNameSecret='" + userName + '\'' +
          '}';
    }
}

UserRepository.java

public class UserRepository {
    String getUserNameForUserId(Integer userId) {
        return UUID.randomUUID().toString();
    }
}

ThreadLocalWithUserContext.java

public class ThreadLocalWithUserContext implements Runnable {
   
    private static final ThreadLocal<Context> userContext = new ThreadLocal<>();
    private final Integer userId;
    private UserRepository userRepository = new UserRepository();

    ThreadLocalWithUserContext(Integer userId) {
        this.userId = userId;
    }


    @Override
    public void run() {
        String userName = userRepository.getUserNameForUserId(userId);
        userContext.set(new Context(userName));
        System.out.println("thread context for given userId: " + userId + " is: " + userContext.get());
    }
}

ThreadLocalTest.java

我们可以通过启动两个线程来测试它,这两个线程将为一个给定的userId执行动作。

public class ThreadLocalTest{

     public static void main(String []args){
        ThreadLocalWithUserContext firstUser  = new ThreadLocalWithUserContext(1);
        ThreadLocalWithUserContext secondUser  = new ThreadLocalWithUserContext(2);
        new Thread(firstUser).start();
        new Thread(secondUser).start();
     }
}

运行这段代码后,我们会在标准输出上看到ThreadLocal是按给定的线程设置的。

输出。

thread context for given userId: 1 is: Context{userNameSecret='18a78f8e-24d2-4abf-91d6-79eaa198123f'}
thread context for given userId: 2 is: Context{userNameSecret='e19f6a0a-253e-423e-8b2b-bca1f471ae5c'}

我们可以看到,每个用户都有自己的Context。

###参考

https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html

相关文章