Three, thread local storage mode: no sharing, no harm

How to use ThreadLocal

The following static class ThreadId assigns a unique ThreadId to each thread. If a thread calls ThreadId’s get() method twice, the return value of the get() method is the same. But if two threads call ThreadId’s get() method separately, the two threads will see a different return value from the get() method. If you’re new to ThreadLocal, you might wonder why calls to get() from the same thread get the same result, but calls to get() from different threads get different results.

static class ThreadId { static final AtomicLong nextId=new AtomicLong(0); Static final ThreadLocal<Long> tl= threadLocal.withInitial (()-> nextid.getAndIncrement ()); Static long get(){return tl.get(); // This method assigns a unique Id to each thread. }}Copy the code

This strange result is all the work of ThreadLocal, but before going into more detail about how ThreadLocal works, let’s take a look at a practical example of what ThreadLocal might look like. You probably know that SimpleDateFormat is not thread-safe, so what do you do if you need to use it in concurrent scenarios?

One solution is to use ThreadLocal. The following example is an implementation of the ThreadLocal solution. This code is highly similar to ThreadId’s code. Different threads calling SafeDateFormat’s get() method will return different instances of SimpleDateFormat objects, which, like local variables, are thread-safe because different threads do not share SimpleDateFormat.

Static class SafeDateFormat {static final ThreadLocal<DateFormat> tl= threadlocal. withInitial(()->  new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss")); static DateFormat get(){ return tl.get(); DateFormat df = safeDateformat.get (); DateFormat = safeDateformat.get ();Copy the code

Now that you have a good idea of how ThreadLocal works, let’s explain in detail how ThreadLocal works.

How ThreadLocal works

Before explaining how ThreadLocal works, think for yourself: If you were to implement ThreadLocal, what would you design for it? The goal of a ThreadLocal is to have different variables V for different threads. The most straightforward way to do this is to create a Map whose Key is the thread and Value is the variable V owned by each thread. You can refer to the diagram and sample code below to understand.

class MyThreadLocal<T> { Map<Thread, T> locals = new ConcurrentHashMap<>(); T get() {return instance.get (thread.currentThread ()); } void set(T T) {locals.put(thread.currentThread (), T); }}Copy the code

Is that how Java ThreadLocal is implemented? This time our design idea is quite different from the Java implementation. Java implementations also have a Map called ThreadLocalMap, but ThreadLocalMap is held by Thread instead of ThreadLocal. The class Thread has a private property called threadLocals, which is of type ThreadLocalMap and whose Key is ThreadLocal. You can understand this by combining the following diagram with the simplified Java implementation code.

Class Thread {/ / internal hold ThreadLocalMap ThreadLocal. ThreadLocalMap threadLocals. } class ThreadLocal<T>{public T get() {ThreadLocalMap ThreadLocalMap map = thread.currentThread () .threadLocals; // In ThreadLocalMap // find the variable Entry e = map.getentry (this); return e.value; } static class ThreadLocalMap{// Internal array instead of Map Entry[] table; Entry getEntry(ThreadLocal key){// Omit lookup logic} //Entry defines static class Entry extends WeakReference<ThreadLocal>{ Object value; }}}Copy the code

At first glance, the only difference between our design and the Java implementation is that the Map belongs to ThreadLocal, while the Java implementation ThreadLocalMap belongs to Thread. Which of these two approaches is more reasonable? Obviously, the Java implementation makes more sense. In the Java implementation, ThreadLocal is simply a proxy utility class that does not hold any thread-related data. All thread-related data is stored in the Thread, which is easy to understand. In terms of data affinity, it is more reasonable for ThreadLocalMap to belong to Thread.

Of course, there is a deeper reason, that is not prone to memory leaks. In our design, the Map held by ThreadLocal holds references to Thread objects, which means that Thread objects in the Map are never reclaimed as long as ThreadLocal objects exist. ThreadLocal lives tend to be longer than threads, so this design can easily lead to memory leaks. In the Java implementation, threads hold ThreadLocalMap, and the reference to ThreadLocal in ThreadLocalMap is still WeakReference, so as long as the Thread object can be reclaimed, ThreadLocalMap can then be reclaimed. This implementation of Java, while more complex, is more secure.

Java’s implementation of ThreadLocal should be considered thoughtful, but even that doesn’t guarantee programmers against memory leaks, such as using ThreadLocal in thread pools, which can result if they’re not careful.

ThreadLocal and memory leaks

Why might using ThreadLocal in a thread pool cause a memory leak? The reason for this is that threads live too long in Thread pools and are often co-existing with applications, which means that threadLocalMaps held by threads are never collected. In addition, an Entry in a ThreadLocalMap is a WeakReference to a ThreadLocal, so it can be reclaimed once the ThreadLocal ends its life cycle. However, the Value in an Entry is strongly referenced by the Entry. Therefore, the Value cannot be reclaimed even after its life cycle ends, resulting in memory leakage.

So how do we use ThreadLocal properly in thread pools? The JVM can’t automatically release strong references to Value, so we can do it manually. How do you do that manually? The try{}finally{} scheme immediately comes to mind. This is a great way to release resources manually. Example code is as follows, you can refer to learn.

ExecutorService es; ThreadLocal tl; Es.execute (()->{//ThreadLocal increment tl.set(obj); Try {// omit business logic code}finally {// manually clean ThreadLocal tl.remove(); }});Copy the code

InheritableThreadLocal with inheritance

Thread variables created with ThreadLocal are not inherited by their children. That is, you create a thread variable V in a ThreadLocal thread, and that thread creates a child thread. You cannot access the parent thread variable V in the child thread through ThreadLocal.

What if you need a child thread to inherit a thread variable from a parent thread? InheritableThreadLocal is an InheritableThreadLocal subclass that uses the same functionality as ThreadLocal.

However, I don’t recommend using InheritableThreadLocal in thread pools at all, not only because it has the same drawbacks as ThreadLocal — the potential for memory leaks — but more importantly because: The creation of threads in a thread pool is dynamic, which can easily lead to inheritance miscalculations if your business logic relies on InheritableThreadLocal, which is often more critical than a memory leak.

4. The System of System Suspension

Not long ago, a colleague ran into a problem at work and developed a Web project: the Web version of the File browser, which allows users to view directories and files on the server in the browser. The project relies on the file browsing service provided by o&M, which only supports message queue (MQ) access. Message queues are widely used in large Internet factories mainly for traffic peak cutting and system decoupling. In this access mode, the two operations of sending messages and consuming results are asynchronous, as you can see in the following diagram.

In my colleague’s Web project, a user sends a request through the browser, which is converted into an asynchronous message sent to MQ, which returns the result and then returns it to the browser. Q: The thread that sends the message to MQ is thread T1, which processes the Web request, but the thread that consumes the MQ result is not thread T1. How can thread T1 wait for the result of MQ? To make this scenario easier for you to understand, I have coded it as follows.

class Message{ String id; String content; Void onMessage(Message MSG){// void onMessage(Message){// void onMessage(Message){// void onMessage(Message) Msg1 = new Message msg1 = new Message msg1 = new Message msg1 = new Message("1","{... } "); // Send a message to msg1; // How do I wait for messages back from MQ? String result = ... ; }Copy the code

This is an asynchronous to synchronous problem.

Guarded Suspension model

Its literal translation is “protective Suspension.”

The following figure shows the Guarded Suspension mode structure. It is very simple. An object GuardedObject has a member variable inside itProtected objects, and two member methods —get(Predicate<T> p)andonChanged(T obj)Methods. The object GuardedObject is the lobby manager we mentioned earlier, and the protected object is the private room in the restaurant; The get() method of the protected object corresponds to our dining. The premise of dining is that the private room has been cleaned up. Parameter P is used to describe this premise. The onChanged() method of the protected object, which corresponds to the server cleaning up the room, fires an event that often changes the calculation of the prerequisite p. In the picture below, the green thread on the left is the customer who needs to eat, and the blue thread on the right is the waiter who cleans up the private room.The internal implementation of GuardedObject is very simple and is a classic use of a pipe procedure. You can refer to the following example code, the core of which is: The get() method waits with await() on the condition variable, and the onChanged() method wakes with signalAll() on the condition variable. The logic is pretty simple, so I won’t go into detail here.

Class GuardedObject<T>{// Protected object T obj; final Lock lock = new ReentrantLock(); final Condition done = lock.newCondition(); final int timeout=1; // Get (Predicate<T> p) {lock.lock(); Try {//MESA procedures recommend writing while(! p.test(obj)){ done.await(timeout, TimeUnit.SECONDS); } }catch(InterruptedException e){ throw new RuntimeException(e); }finally{ lock.unlock(); } // return a non-empty protected object. } // Event notification method void onChanged(T obj) {lock.lock(); try { this.obj = obj; done.signalAll(); } finally { lock.unlock(); }}}Copy the code

Expand the System Suspension mode

Above, we introduced the System of “Gates Suspension” and its implementation. The system can simulate the role of the lobby managers in the real world. Now, we will try to find out whether the “lobby managers” can solve the problems encountered by Mr. Gray.

In the Guarded Suspension mode, GuardedObject has two core methods: the get() method and the onChanged() method. Obviously, in handleWebReq(), the method that handles Web requests, you can call GuardedObject’s get() method to wait; In the consuming method onMessage() of the MQ message, the onChanged() method of GuardedObject can be called to wake up.

Respond handleWebReq(){msg1 = new Message("1","{... } "); // Send a message to msg1; GuardedObject<Message> go =new GuardObjec<>(); Message r = go.get( t->t ! = null); } void onMessage(Message MSG){// how to find a match go? GuardedObject<Message> go=??? go.onChanged(msg); }Copy the code

HandleWebReq () creates an instance of GuardedObject, go, and calls its get() side to wait for the result. So in the onMessage() method, How do I find a GuardedObject that matches? This process is similar to the waiter telling the lobby manager that so-and-so private room has been cleaned up, and the lobby manager how to find people to eat according to the private room. In the real world, the lobby manager has a mental diagram of the relationship between the private room and the diner, so the lobby manager can find out the diner immediately after the waiter finishes talking.

We can refer to the ways of the lobby managers to find out the men to have a try to expand the system of “system of Suspension” so that it will be very convenient for the students to find a solution. In Little Grey’s program, each message sent to MQ has a unique attribute ID, so we can maintain a relationship between MQ message ID and GuardedObject object instance, which is analogous to the relationship between the dining room and the dining room maintained in the lobby manager’s mind.

So with this relationship, let’s see how it works. The following example code extends the Guarded Suspension mode. After the expansion, GuardedObject maintains a Map internally. Its Key is the MQ message ID and its Value is the GuardedObject instance. Static methods create() and fireEvent() were added; The create() method is used to create an instance of GuardedObject and add it to the Map based on the key value, while the fireEvent() method is used to simulate the lobby manager’s logic of finding diners based on the private room.

Class GuardedObject<T>{// Protected object T obj; final Lock lock = new ReentrantLock(); final Condition done = lock.newCondition(); final int timeout=2; // Save all GuardedObject final static Map<Object, GuardedObject> gos=new ConcurrentHashMap<>(); GuardedObject Static <K> GuardedObject create(K key){GuardedObject go=new GuardedObject(); gos.put(key, go); return go; } static <K, T> void fireEvent(K key, T obj){ GuardedObject go=gos.remove(key); if (go ! = null){ go.onChanged(obj); }} // Get (Predicate<T> p) {lock.lock(); Try {//MESA procedures recommend writing while(! p.test(obj)){ done.await(timeout, TimeUnit.SECONDS); } }catch(InterruptedException e){ throw new RuntimeException(e); }finally{ lock.unlock(); } // return a non-empty protected object. } // Event notification method void onChanged(T obj) {lock.lock(); try { this.obj = obj; done.signalAll(); } finally { lock.unlock(); }}}Copy the code

This makes it easy to use GuardedObject to solve the gray student’s problem, as shown below.

Responder handleWebReq(){int id= responder. Get (); Msg1 = new Message(id,"{... } "); // Create GuardedObject GuardedObject<Message> go= Guardedobject. create(id); // Send a message to msg1; Message r = go.get(t->t! = null); } void onMessage(Message MSG){// Wake up the waiting thread guardedobject.fireEvent (MSG. }Copy the code


The system was also called “Guarded Wait” or “Spin Lock” (because of the use of a while loop). These were notable names, but it had a more discreet name: the multithreaded version of the “If”. In a single-threaded scenario, there is no need for if statements to wait, because with only one thread, if that thread is blocked, there are no other active threads, meaning that the result of the if condition does not change. But in multithreaded scenarios, waiting makes sense, where the result of the if judgment condition can change.

5. Balking mode

We mentioned that the “multithreaded version of if” could be used to understand the Guarded Suspension mode. Different from the “single-threaded version of IF”, the “multithreaded version of if” had to wait until the conditions were true and kept persisting. But not all scenarios require this kind of dedication, and sometimes we need to give up quickly.

One of the most common examples of quick abandonment is the autosave functionality provided by various editors. The implementation logic of the automatic saving function is generally to automatically perform the saving operation at certain intervals. The premise of saving operation is that the file has been modified. If the file has not been modified, it needs to give up the saving operation quickly. The AutoSaveEditor class is not thread-safe because it does not use synchronization to read or write the shared variable changed. How can AutoSaveEditor be thread-safe?

Class AutoSaveEditor{// Whether the file has changed Boolean changed=false; / / timing task thread pool ScheduledExecutorService ses = Executors. NewSingleThreadScheduledExecutor (); / / regularly perform automatically save void startAutoSave () {ses. ScheduleWithFixedDelay (() - > {autoSave (); }, 5, 5, TimeUnit.SECONDS); } void autoSave(){if (! changed) { return; } changed = false; // execute the save operation // omit and implement this.execsave (); } // Edit operation void edit(){// omit edit logic...... changed = true; }}Copy the code

To solve this problem, you must have a handy solution: the autoSave() and edit() methods that read and write the changed shared variable are mutexes. This is simple, but performs poorly because the lock scope is too large. We can narrow the scope of the lock and lock only where we read or write the shared variable changed, as shown below.

Void autoSave(){synchronized(this){if (! changed) { return; } changed = false; } // execute the save operation // omit and implement this.execsave (); } // Edit operation void edit(){// omit edit logic...... synchronized(this){ changed = true; }}Copy the code

If you deeply analyze the example application, you will find the sample of the Shared variables is a state variable, the state of the business logic is dependent on the state variables: when state satisfy some condition, performs some business logic, its essence but is actually an if, in a multithreaded scenario, is a kind of “if” multi-threading versions. This “multithreaded version of if” is used in so many different ways that it has been generalized into a design pattern called the Balking pattern.

Classic implementation of Balking pattern

Balking mode is essentially a normalized solution to the “multi-threaded version of if”. For the autosave example above, the normalized Balking mode is written as follows, You’ll notice that simply extracting the assignment of the shared variable changed from the Edit () method into change() has the benefit of separating the concurrent processing logic from the business logic.

boolean changed=false; Void autoSave(){synchronized(this){if (! changed) { return; } changed = false; } // execute the save operation // omit and implement this.execsave (); } // Edit operation void edit(){// omit edit logic...... change(); } // Change status void change(){synchronized(this){changed = true; }}Copy the code

Implement the Balking pattern with volatile

Synchronized Balking is the safest way to implement Balking. It is recommended that you use this method in your actual work. In certain scenarios, volatile can be used, but only if atomicity is not required.

In the frame of the RPC, the local routing table is to make a information synchronization, and registry application startup, the application will depend on the service of the routing table from registry synchronized to the local routing table, if the application to restart the registry downtime, then leads to the application service is not available, because I couldn’t find rely on the service routing table. To prevent this extreme situation, the RPC framework can automatically save the local routing table to a local file, and if the registry goes down during a restart, restore the routing table from the local file before the restart. This is also a demotion plan.

The auto-saving of the routing table is the same as the auto-saving of the editor described above. It can also be implemented in Balking mode, but in this case it is volatile, as shown below. Volatile is possible because there is no atomicity requirement for writes to the shared variables changed and RT, And scheduleWithFixedDelay() ensures that only one thread executes autoSave() at a time.

Public class RouterTable {//Key: interface name //Value: ConcurrentHashMap<String, CopyOnWriteArraySet<Router>> rt = new ConcurrentHashMap<>(); Volatile Boolean changed; / / the thread pool to write routing table into the local files ScheduledExecutorService ses = Executors. NewSingleThreadScheduledExecutor (); / / start timing task / / will change the routing table after the write to local file public void startLocalSaver () {ses. ScheduleWithFixedDelay (() - > {autoSave (); }, 1, 1, MINUTES); } void autoSave() {if (! changed) { return; } changed = false; // write the routing table to a local file // omit its method to implement this.save2local (); Public void remove(Router Router) {Set<Router> Set =rt.get(router.iface); if (set ! = null) { set.remove(router); // Routing table changed = true; Public void add(Router Router) {Set<Router> Set = rt.computeIfAbsent(route.iface, r -> new CopyOnWriteArraySet<>()); set.add(router); // Routing table changed = true; }}Copy the code

A very typical application of the Balking pattern is single initialization, which is implemented in the following example code. In this implementation, we declare init() as a synchronous method so that only one thread can execute the init() method at any one time; The init() method sets inited to true after the first execution, so that subsequent threads that execute init() do not execute doInit() again.

class InitTest{ boolean inited = false; synchronized void init(){ if(inited){ return; } // omit doInit implementation doInit(); inited=true; }}Copy the code

Thread-safe singletons are essentially single-initialization, so you can implement thread-safe singletons using the Balking pattern, which is implemented in the sample code below. This implementation is functionally sound, but performs poorly because synchronized serializes the getInstance() method. Is there a way to optimize its performance?

class Singleton{ private static Singleton singleton; Private Singleton(){} public synchronized static Singleton getInstance(){if(Singleton == null){ singleton=new Singleton(); } return singleton; }}Copy the code

There is, of course, the classic Double Check scheme, which is implemented in detail in the following example code. In the double-checked scenario, synchronized(Singleton. Class){} code is not executed once the Singleton object has been successfully created, that is, the execution path of the getInstance() method is unlocked, solving the performance problem. Note, however, that this scenario uses volatile to disable compilation optimizations. As for the secondary check after obtaining the lock, it is out of responsibility for security.

class Singleton{ private static volatile Singleton singleton; Private Singleton() {} public static Singleton getInstance() {if(Singleton ==null){ Singleton =new Singleton(); // Synchronize {Singleton =new Singleton(); } } } return singleton; }}Copy the code


The classic implementation of Balking is to use mutex. You can use synchronized built-in in the Java language or Lock with the SDK. If you are not satisfied with the performance of the mutex, you can use volatile, but you need to be more cautious.

Of course, you can also try to optimize performance by double-checking. The first check of double-checking is purely for performance reasons: avoid locking because locking is time-consuming. And the second inspection after the lock, it is out of responsibility for security. Double-checking schemes are often used to optimize locking performance.