Thread-Local Variables
A thread-local variable is a variable of type ThreadLocal. Each thread that access a thread-local variable has its own,
        independently initialized copy of the variable. To write or read a thread-local variable's
        value, call its set or get method, respectively.
        Typically, a thread-local variable is declared as a final static field so
        that many components can reach it easily.
               
In the following example, the class TLDBConn represents a
            database connection. The TLBDBConn::open method prints a string and the
            user's name. The class TLServer represents the database itself. It
            contains one method, TLServer::fetchOrder, which returns a string
            containing the user's name. The class TLApplication creates several
                TLDBConn objects, each created with a different user and each in
            its own thread. The TLApplication::testConnection randomly varies the
            duration of the thread so that the threads have a chance to run concurrently.
               
Figure 14-6 User.java
public class User {
    
    public String name;
    
    public User(String n) {
        name = n;
    }
}Figure 14-7 TLDBConn.java
public class TLDBConn {
    
    final static ThreadLocal<User> TLUSER = new ThreadLocal<>();
    
    public static String open(String info) {
        System.out.println(info + ": " + TLUSER.get().name);
        return info + ": " + TLUSER.get().name;
    }
}Figure 14-8 TLServer.java
public class TLServer {
    public static String fetchOrder() {
        return "Fetching order for " + TLDBConn.TLUSER.get().name;
    }
}Figure 14-9 TLApplication.java
import java.util.*;
public class TLApplication {
    
    public void testConnection(User u) {
        
        Runnable r = () -> {
            TLDBConn.TLUSER.set(u);
            TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
            System.out.println(TLServer.fetchOrder());
            
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                        
            TLDBConn.TLUSER.set(new User(u.name + " renamed"));
            TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");            
        };
        
        Thread t = new Thread(r, u.name);
        t.start();
        
    }
    
    public static void main(String[] args) {
        
        TLApplication myApp = new TLApplication();
        
        for(int i=0 ; i<5; i++) {
            myApp.testConnection(new User("user" + i));
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}TLApplication prints output similar to the following:
               
Thread user0, testConnection: user0
Fetching order for user0
Thread user1, testConnection: user1
Fetching order for user1
Thread user2, testConnection: user2
Fetching order for user2
Thread user0, testConnection: user0 renamed
Thread user1, testConnection: user1 renamed
Thread user2, testConnection: user2 renamed
Thread user3, testConnection: user3
Fetching order for user3
Thread user3, testConnection: user3 renamed
Thread user4, testConnection: user4
Fetching order for user4
Thread user4, testConnection: user4 renamedNote that even though the member variable TLDBConn.USER is declared as
                final static, its value is unique for each thread created by
                TLApplication.
               
Also, note that the TLServer::fetchOrder method has no parameters. It
            doesn't require code, in particular, TLApplication::testConnection to
            pass it a User parameter. TLServer::fetchOrder can
            directly access the TLDBConn.USER thread-local variable that
            corresponds to the thread in which it's running:
               
        return "Fetching order for " + TLDBConn.TLUSER.get().name;Consequently, thread-local variables enable you to hide method arguments.
Inheriting Thread-Local Variables
When a parent thread starts a child thread, none of the values of the parent thread's thread-local variables are inherited by the child thread. However, if you want a child thread to inherit the values of its parent's thread-local values, then create a thread-local variable with the InheritableThreadLocal class instead.
The following example includes a InheritableThreadLocal variabled
            named TLADMIN in addition to the ThreadLocal named
                TLUSER:
                  
Figure 14-10 TLDBConn.java
public class TLDBConn {
    
    final static ThreadLocal<User> TLUSER = new ThreadLocal<>();
    final static InheritableThreadLocal<User> TLADMIN = new InheritableThreadLocal<>();
    
    public static String open(String info) {
        System.out.println(info + ": " + TLUSER.get().name);
        return info + ": " + TLUSER.get().name;
    }
}The following method starts a thread named childThread within a thread.
            The thread childThread retrieves the value of the
                InheritableThreadLocal variable named TLADMIN and
            attempts to retrieve the value of the ThreadLocal variable named
                TLUSER:
                  
    public void testConnectionWithInheritableTL(User u) {
        
        Runnable r = () -> {
            TLDBConn.TLUSER.set(u);
            TLDBConn.TLADMIN.set(new User("Admin"));
            TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
            System.out.println(TLServer.fetchOrder());
            
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            Thread childThread = new Thread(
                () -> {
                    System.out.println("Child thread");
                    System.out.println("TLADMIN: " + TLDBConn.TLADMIN.get().name);
                    try {
                        System.out.println("TLUSER: " + TLDBConn.TLUSER.get().name);
                    } catch (NullPointerException e) {
                        System.out.println("NullPointerException: TLUSER hasn't beet set");
                    }
                }
            );
            childThread.start();
                    
                        
            TLDBConn.TLUSER.set(new User(u.name + " renamed"));
            TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");            
        };
        
        Thread t = new Thread(r, u.name);
        t.start();
    }When you call this method, the following statement in the instantiation of
                childThread throws a NullPointerException:
                  
System.out.println("TLUSER: " + TLDBConn.TLUSER.get().name);The value of the ThreadLocal variable TLUSER hasn't
            been inherited by childThread. However, the value of the
                InheritableThreadLocal variable TLADMIN has been
            inherited by childThread. When childThread is started,
            it prints the following output:
                  
Child thread
TLADMIN: Admin
NullPointerException: TLUSER hasn't beet setIssues with Thread-Local Variables
Unfortunately, thread-local variables have some design flaws.
Note:
Scoped Values can address these issues with thread-local variables.Unconstrained Mutability
Every thread-local variable is mutable. This might make it
                                difficult to discern in your application's code which components
                                update the shared state and in what order. In the example described
                                in Thread-Local Variables, TLApplication::testConnection reassigns
                                        TLDBConn.TLUSER with a new value:
                     
        Runnable r = () -> {
            TLDBConn.TLUSER.set(u);
            TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");
            System.out.println(TLServer.fetchOrder());
            // ...            
            TLDBConn.TLUSER.set(new User(u.name + " renamed"));
            TLDBConn.open("Thread " + Thread.currentThread().getName() + ", testConnection");            
        };Unbounded Lifetime
The Java runtime retains a thread's incarnation of a
                                thread-local variable for the lifetime of the thread or until code
                                in the thread calls the thread local variable's
                                        remove method. If you omit calling this
                                method, then the Java runtime might retain thread data longer than
                                necessary. If you're using a thread pool, then a value in a
                                thread-local variable set in one task might leak into another task.
                                If you have set the value of a thread-local variable multiple times
                                in a thread, then there might not be a clear point when it's safe
                                for the thread to call the remove method, which may
                                cause a long-term memory leak.
                     
Expensive Inheritance
The overhead of thread-local variables may be worse when using large numbers of threads because thread-local variables of a parent thread can be inherited by child threads.