In this lesson, we'll talk generally about working with the java.lang.ThreadLocal<> class and how to use it in a multithreaded environment.
The ThreadLocal class is used to store variables. A distinctive feature of this class is that it keeps a separate independent copy of a value for each thread using it.
Delving deeper into the operation of the class, we can imagine a Map that maps threads to values, from which the current thread takes the appropriate value when it needs to use it.
ThreadLocal class constructor
Constructor | Action |
---|---|
ThreadLocal() | Creates an empty variable in Java |
Methods
Method | Action |
---|---|
get() | Returns the value of the current thread's local variable |
set() | Sets the value of the local variable for the current thread |
remove() | Removes the value of the local variable of the current thread |
ThreadLocal.withInitial() | Additional factory method that sets the initial value |
get() & set()
Let's write an example where we create two counters. The first, an ordinary variable, will be for counting the number of threads. The second we will wrap in a ThreadLocal. And we'll see how they work together. First, let's write a ThreadDemo class that inherits Runnable and contains our data and the all-important run() method. We'll also add a method for displaying the counters on the screen:
class ThreadDemo implements Runnable {
int counter;
ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<>();
public void run() {
counter++;
if(threadLocalCounter.get() != null) {
threadLocalCounter.set(threadLocalCounter.get() + 1);
} else {
threadLocalCounter.set(0);
}
printCounters();
}
public void printCounters(){
System.out.println("Counter: " + counter);
System.out.println("threadLocalCounter: " + threadLocalCounter.get());
}
}
With each run of our class, we increase the counter variable call the get() method to get data from the ThreadLocal variable. If the new thread has no data, then we will set it to 0. If there is data, we will increase it by one. And let's write our main method:
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread t1 = new Thread(threadDemo);
Thread t2 = new Thread(threadDemo);
Thread t3 = new Thread(threadDemo);
t1.start();
t2.start();
t3.start();
}
Running our class, we see that the ThreadLocal variable remains the same regardless of the thread that accesses it, but the number of threads grows.
Counter: 2
Counter: 3
threadLocalCounter: 0
threadLocalCounter: 0
threadLocalCounter: 0
Process finished with exit code 0
remove()
To understand how the remove method works, we'll just slightly change the code in the ThreadDemo class:
if(threadLocalCounter.get() != null) {
threadLocalCounter.set(threadLocalCounter.get() + 1);
} else {
if (counter % 2 == 0) {
threadLocalCounter.remove();
} else {
threadLocalCounter.set(0);
}
}
In this code, if the thread counter is an even number, then we will call the remove() method on our ThreadLocal variable. Result:
threadLocalCounter: 0
Counter: 2
threadLocalCounter: null
Counter: 1
threadLocalCounter: 0
Process finished with exit code 0
And here we easily see that the ThreadLocal variable in the second thread is null.
ThreadLocal.withInitial()
This method creates a thread-local variable.
Implementation of the ThreadDemo class:
class ThreadDemo implements Runnable {
int counter;
ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 1);
public void run() {
counter++;
printCounters();
}
public void printCounters(){
System.out.println("Counter: " + counter);
System.out.println("threadLocalCounter: " + threadLocalCounter.get());
}
}
And we can look at the result of our code:
Counter: 2
Counter: 3
threadLocalCounter: 1
threadLocalCounter: 1
threadLocalCounter: 1
Process finished with exit code 0
Why should we use such variables?
ThreadLocal provides an abstraction over local variables in relation to the thread of execution java.lang.Thread.
ThreadLocal variables differ from ordinary ones in that each thread has its own, individually initialized instance of the variable, which is accessed via the get() and set() methods.
Each thread, i.e. instance of the Thread class, has a map of ThreadLocal variables associated with it. The map's keys are references to ThreadLocal objects, and the values are references to "acquired" ThreadLocal variables.
Why is the Random class not suitable for generating random numbers in multithreaded applications?
We use the Random class to get random numbers. But does it work just as well in a multithreaded environment? Actually, no. Random is not suitable for multithreaded environments, because when multiple threads access a class at the same time, performance suffers.
To address this problem, JDK 7 introduced the java.util.concurrent.ThreadLocalRandom class to generate random numbers in a multithreaded environment. It consists of two classes: ThreadLocal and Random.
The random numbers received by one thread are independent of other threads, but java.util.Random provides globally random numbers. Also, unlike Random, ThreadLocalRandom does not support explicit seeding. Instead, it overrides the setSeed() method inherited from Random, so that it always throws an UnsupportedOperationException when called.
Let's look at the methods of the ThreadLocalRandom class:
Method | Action |
---|---|
ThreadLocalRandom current() | Returns the ThreadLocalRandom of the current thread. |
int next(int bits) | Generates the next pseudo-random number. |
double nextDouble(double least, double bound) | Returns a pseudorandom number from a uniform distribution between least (inclusive) and bound (exclusive). |
int nextInt(int least, int bound) | Returns a pseudorandom number from a uniform distribution between least (inclusive) and bound (exclusive). |
long nextLong(long n) | Returns a pseudorandom number from a uniform distribution between 0 (inclusive) and the specified value (exclusive). |
long nextLong(long least, long bound) | Returns a pseudorandom number from a uniform distribution between least (inclusive) and bound (exclusive). |
void setSeed(long seed) | Throws UnsupportedOperationException. This generator does not support seeding. |
Getting random numbers using ThreadLocalRandom.current()
ThreadLocalRandom is a combination of the ThreadLocal and Random classes. It achieves better performance in a multithreaded environment by simply avoiding any concurrent access to instances of the Random class.
Let's implement an example involving multiple threads and see our application does with the ThreadLocalRandom class:
import java.util.concurrent.ThreadLocalRandom;
class RandomNumbers extends Thread {
public void run() {
try {
int bound = 100;
int result = ThreadLocalRandom.current().nextInt(bound);
System.out.println("Thread " + Thread.currentThread().getId() + " generated " + result);
}
catch (Exception e) {
System.out.println("Exception");
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
RandomNumbers randomNumbers = new RandomNumbers();
randomNumbers.start();
}
long endTime = System.currentTimeMillis();
System.out.println("Time taken: " + (endTime - startTime));
}
}
Result of our program:
Thread 17 generated 13
Thread 18 generated 41
Thread 16 generated 99
Thread 19 generated 25
Thread 23 generated 33
Thread 24 generated 21
Thread 15 generated 15
Thread 21 generated 28
Thread 22 generated 97
Thread 20 generated 33
And now let's change our RandomNumbers class and use Random in it:
int result = new Random().nextInt(bound);
Thread 20 generated 48
Thread 19 generated 57
Thread 18 generated 90
Thread 22 generated 43
Thread 24 generated 7
Thread 23 generated 63
Thread 15 generated 2
Thread 16 generated 40
Thread 17 generated 29
Thread 21 generated 12
Take note! In our tests, sometimes the results were the same and sometimes they were different. But if we use more threads (say, 100), the result will look like this: Random — 19-25 ms
ThreadLocalRandom — 17-19 ms Accordingly, the more threads in our application, the greater the performance hit when using the Random class in a multithreaded environment. |
To sum up and reiterate the differences between the Random and ThreadLocalRandom classes:
Random | ThreadLocalRandom |
---|---|
If different threads use the same instance of Random, there will be conflicts and performance will suffer. | There are no conflicts or problems, because the generated random numbers are local to the current thread. |
Uses a linear congruential formula to change the initial value. | The random number generator is initialized using an internally generated seed. |
Useful in applications where each thread uses its own set of Random objects. | Useful in applications where multiple threads use random numbers in parallel in thread pools. |
This is a parent class. | This is a child class. |
GO TO FULL VERSION