One way to prevent concurrent modifications from different Threads is to given them their own local data. Java ThreadLocal allows to do thread data separation.
ThreadLocal holder
Let’s write a simple class that will hold ThreadLocal variable for each thread. We’ll use ThreadLocal.withInitial(Supplier<? extends T>) static factory method to create an instance of ThreadLocal with initialValue throwing exception, because in our case having customer is required:
package com.farenda.java.lang; import java.util.function.Supplier; public class CurrentCustomer { // No customer is logical error, so we prevent that: private static final Supplier<String> STATE_CHECKER = () -> { throw new IllegalStateException("Customer must be set!"); }; private static final ThreadLocal<String> customer = ThreadLocal.withInitial(STATE_CHECKER); public static String get() { return customer.get(); } public static void set(String id) { customer.set(id); } }
Note that access to customer ThreadLocal through get() and set(String id) methods is not synchronized. ThreadLocal takes care of that.
Example Java Application
To test the above code we’ll use ExecutorService to start a few worker threads each of which will process own customer.
Worker accessing ThreadLocal
Simple Worker thread that stores current customer in ThreadLocal variable, does processing (calling a number of layers below that access the same ThreadLocal variable) and shows current customer id at the end:
package com.farenda.java.lang; import java.util.concurrent.TimeUnit; public class Worker implements Runnable { private final String customer; public Worker(String customer) { this.customer = customer; } @Override public void run() { // Each worker sets own customer: CurrentCustomer.set(customer); System.out.printf("Thread %s, customer: %s%n", Thread.currentThread().getName(), CurrentCustomer.get()); sleep(); System.out.printf("%s processed customer: %s%n", Thread.currentThread().getName(), CurrentCustomer.get()); } private void sleep() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { // ignore } } }
Executing worker threads
package com.farenda.java.lang; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadLocalExample { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); executor.execute(new Worker("AAA")); executor.execute(new Worker("BBB")); executor.execute(new Worker("CCC")); executor.shutdown(); } }
The output clearly shows that each Worker is accessing the same customer id at the beginning and at the end of execution:
Thread pool-1-thread-3, customer: CCC Thread pool-1-thread-1, customer: AAA Thread pool-1-thread-2, customer: BBB pool-1-thread-1 processed customer: AAA pool-1-thread-3 processed customer: CCC pool-1-thread-2 processed customer: BBB