Browser docs

Thread-local storage

Intent

Provide an ability to have a copy of a variable for each thread, making it thread-safe.

Explanation

During code assembling compiler add .tdata directive, which means that variables below will store in different places from thread to thread. This means changing variable in one thread won’t affect the same variable in other thread.

Real world example

On constructions each worker has its own shovel. When one of them broke his shovel, other still can continue work due to they have own instrument.

In plain words

Each thread will have its own copy of variable.

Wikipedia says

Thread-local storage (TLS) is a computer programming method that uses static or global memory local to a thread.

Programmatic Example

To define variable in thread-local storage you just need to put it into Java’s ThreadLocal, e.g:

1public class Main {
2    
3    private ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
4    
5    public void initialize(String value) {
6        this.stringThreadLocal.set(value);
7    }
8}

Below we will contact a variable from two threads at a time to see, why we need it.
Main logic that show’s current variable value is imposed in class AbstractThreadLocalExample, two implementations differs only in the value store approach.

Main class:

 1/**
 2 * Class with main per-thread logic.
 3 */
 4public abstract class AbstractThreadLocalExample implements Runnable {
 5
 6    private static final Random RND = new Random();
 7
 8    @Override
 9    public void run() {
10        //Here we stop thread randomly
11        LockSupport.parkNanos(RND.nextInt(1_000_000_000, 2_000_000_000));
12
13        System.out.println(getCurrentThreadName() + ", before value changing: " + getter().get());
14        setter().accept(RND.nextInt());
15    }
16
17    /**
18     * Setter for our value.
19     *
20     * @return consumer
21     */
22    protected abstract Consumer<Integer> setter();
23
24    /**
25     * Getter for our value.
26     *
27     * @return supplier
28     */
29    protected abstract Supplier<Integer> getter();
30
31    private String getCurrentThreadName() {
32        return Thread.currentThread().getName();
33    }
34}

And two implementations. With ThreadLocal:

 1/**
 2 * Example of runnable with use of {@link ThreadLocal}.
 3 */
 4public class WithThreadLocal extends AbstractThreadLocalExample {
 5    
 6    private ThreadLocal<Integer> value;
 7
 8    public WithThreadLocal(ThreadLocal<Integer> value) {
 9        this.value = value;
10    }
11
12    @Override
13    protected Consumer<Integer> setter() {
14        return value::set;
15    }
16
17    @Override
18    protected Supplier<Integer> getter() {
19        return value::get;
20    }
21}

And the class that stores value in one shared for all threads place:

 1/**
 2 * Example of runnable without usage of {@link ThreadLocal}.
 3 */
 4public class WithoutThreadLocal extends AbstractThreadLocalExample {
 5
 6    private Integer value;
 7
 8    public WithoutThreadLocal(Integer value) {
 9        this.value = value;
10    }
11
12    @Override
13    protected Consumer<Integer> setter() {
14        return integer -> value = integer;
15    }
16
17    @Override
18    protected Supplier<Integer> getter() {
19        return () -> value;
20    }
21}

Guess we want to run these classes. We will construct both implementations and invoke run method. So, we want to see initial value in console (thanks to System.out.println()). Let’s take a look at tests.

 1public class ThreadLocalTest {
 2
 3    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
 4    private final PrintStream originalOut = System.out;
 5
 6    @BeforeEach
 7    public void setUpStreams() {
 8        System.setOut(new PrintStream(outContent));
 9    }
10
11    @AfterEach
12    public void restoreStreams() {
13        System.setOut(originalOut);
14    }
15
16    @Test
17    public void withoutThreadLocal() throws InterruptedException {
18        int initialValue = 1234567890;
19
20        int threadSize = 2;
21        ExecutorService executor = Executors.newFixedThreadPool(threadSize);
22
23        WithoutThreadLocal threadLocal = new WithoutThreadLocal(initialValue);
24        for (int i = 0; i < threadSize; i++) {
25            executor.submit(threadLocal);
26        }
27        executor.awaitTermination(3, TimeUnit.SECONDS);
28
29        List<String> lines = outContent.toString().lines().toList();
30        //Matches only first finished thread output, the second has changed by first thread value
31        Assertions.assertFalse(lines.stream()
32                .allMatch(line -> line.endsWith(String.valueOf(initialValue))));
33    }
34
35    @Test
36    public void withThreadLocal() throws InterruptedException {
37        int initialValue = 1234567890;
38
39        int threadSize = 2;
40        ExecutorService executor = Executors.newFixedThreadPool(threadSize);
41
42        WithThreadLocal threadLocal = new WithThreadLocal(ThreadLocal.withInitial(() -> initialValue));
43        for (int i = 0; i < threadSize; i++) {
44            executor.submit(threadLocal);
45        }
46
47        executor.awaitTermination(3, TimeUnit.SECONDS);
48
49        List<String> lines = outContent.toString().lines().toList();
50        Assertions.assertTrue(lines.stream()
51                .allMatch(line -> line.endsWith(String.valueOf(initialValue))));
52    }
53}

The output of test named withThreadLocal:

pool-2-thread-2, before value changing: 1234567890
pool-2-thread-1, before value changing: 1234567890

And the output of withoutThreadLocal:

pool-1-thread-2, before value changing: 1234567890
pool-1-thread-1, before value changing: 848843054

Where 1234567890 - is our initial value. We see, that in test withoutThreadLocal thread 2 got out from LockSupport#parkNanos earlier than the first and change value in shared variable.

Class diagram

classDiagram
    class ThreadLocal
    ThreadLocal : get()
    ThreadLocal : initialValue()
    ThreadLocal : remove()
    ThreadLocal : set(T value)

Applicability

Use ThreadLocal when:

  • You need to run singleton with state in multiple threads
  • You need to use not thread-safe classes in concurrency program

Tutorials

Known uses

  • In java.lang.Thread during thread initialization
  • In java.net.URL to prevent recursive provider lookups
  • In org.junit.runners.BlockJUnit4ClassRunner to contain current rule
  • In org.springframework:spring-web to store request context
  • In org.apache.util.net.Nio2Endpoint to allow detecting if a completion handler completes inline
  • In io.micrometer to avoid problems with not thread-safe NumberFormat

Credits