Starting from the SimpleDateFormat
Let’s start with an example where we create 20 threads that do one thing: switch the time
public class ThreadLoaclExample {
// Not thread safe
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static Date parse(String strDate) throws ParseException {
return sdf.parse(strDate);
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
try {
System.out.println(parse("The 2021-11-18 21:36:17"));
} catch(ParseException e) { e.printStackTrace(); } }).start(); }}}Copy the code
Run it, error reportedThe reason is that SimpleDateFormat is not thread-safe. Click on the SimpleDateFormat source code.DateFormat is not synchronous, and it is recommended to create a separate format instance for each thread, which must be externally synchronized if multiple threads want to access it simultaneously.
Synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized
public static synchronized Date parse(String strDate) throws ParseException {
return sdf.parse(strDate);
}
Copy the code
But doing so certainly degrades performance. Another way to do this is to do thread isolation, as noted in the comment, by creating a single SimpleDateFormat object for each thread, unique to the thread, so that there are no thread safety issues. ThreadLocal = ThreadLocal = ThreadLocal = ThreadLocal
public class ThreadLoaclExample {
private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<>();
private static SimpleDateFormat getDateFormat(a) {
SimpleDateFormat dateFormat = dateFormatThreadLocal.get();// Get a DateFormat from the scope of the current thread
if (dateFormat == null) {
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// Set a simpleDateFormat object in the scope of the current thread
//Thread.currentThread();
dateFormatThreadLocal.set(dateFormat);
}
return dateFormat;
}
public static Date parse(String strDate) throws ParseException {
return getDateFormat().parse(strDate);
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
try {
System.out.println(parse("The 2021-11-18 21:36:17"));
} catch(ParseException e) { e.printStackTrace(); } }).start(); }}}Copy the code
Let’s run it. It’s not an error
SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat: SimpleDateFormat
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(16);
for (int i = 0; i < 1000; i++) {
executorService.execute(() -> {
try {
System.out.println(parse("The 2021-11-18 21:36:17"));
} catch(ParseException e) { e.printStackTrace(); }}); }}Copy the code
The nice thing about this is that you can do 1000 tasks with 16 SimpleDateFormat objects.
This is the first very typical scenario for using ThreadLocal.
The second scenario
The second function is to serve as a context. In this scenario, when a request is received from service-1, the user information is calculated. The following methods, service-2, service-3, and service-4, all require the user information. The code is very redundant as it is passed back and forth.
One solution is to put user information in memory, such as a hashmap, so that service-1 can put user information in, service-2, service-3, service-4 can get user information. This avoids passing user as a parameter.
Then there is another thread concurrency safety issue, when the same thread requests access? Synchronized or ConcurrentHashMap is used to secure the hashMap, both of which affect performance.
The final solution is to use ThreadLocal, which allows each thread to have its own user information, which is thread-safe and can be stored in service-1, service-2, service-3, or service-4.So this is the second function, which acts as the context UserContextHolder, to avoid passing arguments.
The storage location of ThreadLocal
Let’s first look at where threads, ThreadLocal, and ThreadLocalMap are stored.There is a ThreadLocalMap variable in the Thread class, as shown below, to make it thread-specific.
There are many entries in the ThreadLocalMap, and the key of this Entry isWeak reference threadLocal, value is the value that needs to be stored.
The reason why there are multiple entries in a ThreadLocalMap is because we can define multiple ThreadLocalizations when we use them, and the values are ultimately stored as Entry by Entry.
With the above macro feeling, let’s look at the source code analysis, first look at the set method:
public void set(T value) {
// Get the current thread to ensure isolation
Thread t = Thread.currentThread();
// Get ThreadLocalMap based on thread, initialize if not
ThreadLocalMap map = getMap(t);
// If map is not empty, set the value
if(map ! =null)
map.set(this, value);
else // Otherwise create a map
createMap(t, value);
}
Copy the code
If the map is empty, create it first
The initialization process is also relatively simple. Create a new array, compute the position based on the hash value, and place the key and value in that position
ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// The default length is 16
table = new Entry[INITIAL_CAPACITY];
// Compute the array subscript
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// Put the key and value in the I position
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
Copy the code
When we look at map.set, we also calculate the position first. If the position already has a value, which is the key I used before, then we replace the value of the value, if it is null then we execute the replaceStaleEntry method, otherwise we move to the next position.
private void set(ThreadLocal
key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
// Compute the array subscript
int i = key.threadLocalHashCode & (len-1);
// Linear probe
for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// The I position already has a value
if (k == key) {
e.value = value;
return;
}
// If key==null, replaceStaleEntry is performed.
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code
We know that Hashmap uses the zipper method (also known as the chain address method) when conflicts occur, while our ThreadLocalMap uses the linear detection method. If conflicts occur, it does not chain down in the form of a linked list, but continues to look for the next empty grid. If you are interested, please check out ConcurrentHashMap.
Let’s look at the get method, which is also very simple. We get a ThreadLocalMap from the thread, and then we pass this itself as the key from the map to get the Entry, and then we get the value from the Entry.
public T get(a) {
// Get the current thread
Thread t = Thread.currentThread();
// Get the ThreadLocalMap object in the current thread
ThreadLocalMap map = getMap(t);
if(map ! =null) {
// Get the Entry object in ThreadLocalMap and get the Value
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
returnresult; }}ThreadLocalMap is created if it has not been created before
return setInitialValue();
}
Copy the code
If the map is empty, setInitialValue is initialized, which is the same logic as in the set method above.
private T setInitialValue(a) {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Copy the code
Strong and weak references
Strong references and weak references have been mentioned in the title, and the key in the Entry mentioned above is a weak reference of ThreadLocal. Therefore, what is strong reference and what is weak reference are introduced here.
Let’s take a look at the strong reference code:
public class ReferenceExample {
static Object object = new Object();
public static void main(String[] args) {
Object strongRef = object;
object = null; System.gc(); System.out.println(strongRef); }}Copy the code
Run it. It’s not recycled
I’ve drawn a little bit of a picture here, and you can see that object and strongRef both point to new Object() objects in the heap.
After system.gc (), strongRef is still attached to new Object(), so it is not released.Take a look at the code for weak references:
public class ReferenceExample {
static Object object = new Object();
public static void main(String[] args) {
WeakReference<Object> weakRef = new WeakReference<>(object);
object = null; System.gc(); System.out.println(weakRef.get()); }}Copy the code
Execute it again, and the result is null, which has been reclaimed
Weak reference connection is very weak, this dotted line is equal to no, virtually empty, when the new Object() is recycled, it will be directly recycled, so when printing weakRef, it will be null.
When a threadLocal instance =null operation is performed in the business code, we want to clean up the threadLocal instance. As in the above example, after the garbage collection, the key will be null, so the Entry will not occupy the array, so the key==null will be cleared.
For those of you who don’t know much about garbage collection, check out the article on GARBAGE collection.
Memory leak /remove() method
The remove() method must be called when ThreadLocal is finished! Be sure to call the remove() method! Be sure to call the remove() method! Otherwise, it will cause memory leaks.
A memory leak is a memory that cannot be reclaimed when an object is no longer useful. This is called a memory leak.
The Key of leakage
When ThreadLocal instance = null is executed and the key is still referencing the ThreadLocal, the memory will not be freed and the Entry will remain in the array.
The JDK takes this into account. When ThreadLocal’s get, set, remove, and rehash methods are executed, it scans for an Entry with a null key. If the key of an Entry is null, it indicates that its corresponding value is invalid. Therefore, the value object is set to NULL. In this way, the value object can be reclaimed to prevent memory leakage.
The value of the leak
We have solved the key leak, but we know that value is a strong reference. Let’s look at the following call chain:
Thread Ref → Current Thread → ThreadLocalMap → Entry → Value → Value instance that may leak.
If a thread executes a time-consuming task without stopping, and ThreadLocal’s get, set, remove, or rehash methods are not called, then the memory to which the value points will always exist and occupy it. To solve this situation, use the remove method. Take a look at the source:
public void remove(a) {
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
m.remove(this);
}
Copy the code
There is also a danger that if a thread is a thread pool, it does not terminate when it finishes executing code, but simply returns it to the thread pool, and the value in that thread is always occupied and cannot be reclaimed, causing a memory leak. So it’s a good code practice to call remove() when ThreadLocal is no longer in use. Thank you for watching