(译)ThreadLocal简介

简单介绍ThreadLocal的文章,渣翻,见谅!

1. Overview

1.概览

In this article, we will be looking at the ThreadLocal construct from the java.lang package. This gives us the ability to store data individually for the current thread – and simply wrap it within a special type of object.

在这篇文章里,我们将着眼于java.lang package中的ThreadLocal结构。这种结构将给予我们在当前线程中单独存储数据的能力-并以一种特别的对象类型去简单地包装数据。 

2. ThreadLocal API

2.ThreadLocal API

The TheadLocal construct allows us to store data that will be accessible only by a specific thread.

ThreadLocal允许我们去存储只被特定线程访问的数据

Let’s say that we want to have an Integer value that will be bundled with the specific thread:

假设我们想要一个被特定线程绑定的Integer值

1

ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();

Next, when we want to use this value from a thread we only need to call a get() or set()method. Simply put, we can think that ThreadLocal stores data inside of a map – with the thread as the key.

接下来,我们想要在线程中使用这个值,我们仅仅只需要使用get()或者set()方法。我们可以想象ThreadLocal把数据存储在一个Map中,我们只要简单地把线程作为key值进行put操作

Due to that fact, when we call a get() method on the threadLocalValue we will get an Integer value for the requesting thread:

基于以上事实,当我们对threadLocalValue使用get()方法,我们就将得到请求线程的Integer值

1

2

threadLocalValue.set(1);

Integer result = threadLocalValue.get();

We can construct an instance of the ThreadLocal by using the withInitial() static method and passing a supplier to it:

我们能够使用withInitial()构造一个ThreadLocal实例,并传值给它

1

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

To remove value from the ThreadLocal we can call a remove() method:

从threadLocal中移除值,我们可以使用remove()方法

1

threadLocal.remove();

To see how to use the ThreadLocal properly, firstly, we will look at an example that does not use a ThreadLocal, then we will rewrite our example to leverage that construct.

为了明白如何适合地运用ThreadLocal,首先,我们要看一个没有使用ThreadLocal的例子,然后我们用ThreadLocal结构重写这个例子

3. Storing User Data in a Map

3.在map中存储User数据

Let’s consider a program that needs to store the user specific Context data per given user id:

让我们假设需要context存储每一个给定用户id的用户信息

1

2

3

4

5

6

7

public class Context {

    private String userName;

 

    public Context(String userName) {

        this.userName = userName;

    }

}

We want to have one thread per user id. We’ll create a SharedMapWithUserContext class that implements

a Runnable interface. The implementation in the run() method calls some database through the UserRepository class that returns a Context object for a given userId.

我们想要每个用户id拥有一个线程。我们将创建一个实现Runnable接口的SharedMapWithUserContext类。run()方法的实现主要通过UserRepository类调用某个数据库,依据给定的userId返回一个用户信息的Context类。

Next, we store that context in the ConcurrentHashMap keyed by userId:

接下来我们将把userId作为key值在ConcurrentHashMap中存储用户信息

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public class SharedMapWithUserContext implements Runnable {

  

    public static Map<Integer, Context> userContextPerUserId

      = new ConcurrentHashMap<>();

    private Integer userId;

    private UserRepository userRepository = new UserRepository();

 

    @Override

    public void run() {

        String userName = userRepository.getUserNameForUserId(userId);

        userContextPerUserId.put(userId, new Context(userName));

    }

 

    // standard constructor

}

We can easily test our code by creating and starting two threads for two different userIds and asserting that we have two entries in the userContextPerUserId map:

我们可以简单测试我们的代码通过为两个不同的userIds创建并启动两个线程,可以看到userContextPerUserId的map中 有两个值

1

2

3

4

5

6

SharedMapWithUserContext firstUser = new SharedMapWithUserContext(1);

SharedMapWithUserContext secondUser = new SharedMapWithUserContext(2);

new Thread(firstUser).start();

new Thread(secondUser).start();

 

assertEquals(SharedMapWithUserContext.userContextPerUserId.size(), 2);

4. Storing User Data in ThreadLocal

4、在ThreadLocal中存储User数据

We can rewrite our example to store the user Context instance using a ThreadLocal. Each thread will have its own ThreadLocal instance.

我们可以通过使用ThreadLocal重写我们存储用户信息的示例。每个线程将会拥有它们自身的ThreadLocal实例

When using ThreadLocal we need to be very careful because every ThreadLocal instance is associated with a particular thread. In our example, we have a dedicated thread for each particular userId and this thread is created by us so we have full control over it.

当使用ThreadLocal我们需要特别小心,因为每个ThreadLocal实例都被关联到一个特定的线程上。在我们的例子中,对于每一个特定的userId我们都有一个专门的线程对应,然后这些线程由我们创建,因此我们完全地控制它。

The run() method will fetch the user context and store it into the ThreadLocal variable using the set() method:

run()方法将会获取user信息,并通过set()方法将user信息存储到ThreadLocal中去

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public class ThreadLocalWithUserContext implements Runnable {

  

    private static ThreadLocal<Context> userContext

      = new ThreadLocal<>();

    private Integer userId;

    private UserRepository userRepository = new UserRepository();

 

    @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());

    }

     

    // standard constructor

}

We can test it by starting two threads that will execute the action for a given userId:

我们可以通过启动两个给定userId的线程进行测试

1

2

3

4

5

6

ThreadLocalWithUserContext firstUser

  = new ThreadLocalWithUserContext(1);

ThreadLocalWithUserContext secondUser

  = new ThreadLocalWithUserContext(2);

new Thread(firstUser).start();

new Thread(secondUser).start();

After running this code we’ll see on the standard output that ThreadLocal was set per given thread:

运行代码后,我们可以看到每个线程都被设置了一个ThreadLocal

1

2

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'}

We can see that each of the users has its own Context.

我们可以看到每个user都有自己的Context

5. Do not use ThreadLocal with ExecutorService

5.不要在线程池里使用ThreadLocal(可能会导致OutOfMemory)

If we want to use an ExecutorService and submit a Runnable to it, using ThreadLocal will yield non-deterministic results – because we do not have a guarantee that every Runnable action for a given userId will be handled by the same thread every time it is executed.

如果我们想要使用线程池并向其提交一个Runnable,使用ThreadLocal将会产生一个不确定的结果,因为我们无法确保每个给定userId的操作被同一个线程操作。

Because of that, our ThreadLocal will be shared among different userIds. That’s why we should not use a TheadLocal together with ExecutorService. It should only be used when we have full control over which thread will pick which runnable action to execute.

正因如此,我们的ThreadLocal将会被不同userId共享,这就是为什么我们不应该和线程池一起使用ThreadLocal。我们只有在线程与可运行操作可控的情况下

6. Conclusion

In this quick article, we were looking at the ThreadLocal construct. We implemented the logic that uses ConcurrentHashMap that was shared between threads to store the context associated with a particular userId. Next, we rewrote our example to leverage ThreadLocal to store data that is associated with a particular userId and with a particular thread.

在这个简短的文章里,我们着眼于ThreadLocal结构。当我们使用ConcurrentHashMap时,关联userId的context是被线程共享的。在之后,我们重写我们的例子利用ThreadLocal去存储关联特定userId和线程的数据

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值