Three ways to achieve multi-thread safety,
1. Use the lock mechanism synchronize and lock methods: to lock resources, please refer to my previous article.
2. Use java.util.concurrent following class library: There are thread-safe collection classes provided by JDK: AtomicInteger, AtomicStampedReference, ConcurrentHashMap, CopyOnWriteArrayList, ReentrantLock, FutureTask, CountDownLatch.
3. Multi-instance, or multi-copy (ThreadLocal): ThreadLocal can maintain a private local variable for each thread.
Here mainly talk about ThreadLocal:
The idea of ThreadLocal to solve the multi-threaded concurrency problem is very simple: it is to maintain a copy variable for each thread , so that the thread has private resources, and there is no need to compete for resources in the process. Each thread can change its own copy independently without affecting the copies corresponding to other threads.
In a multithreaded environment, how to prevent your own variables from being tampered with by other threads?
api:
void set(T value): Set the value in the current thread copy of this thread local variable to the specified value.
T get(): Returns the value in the current thread copy of this thread local variable.
void remove(): Remove the current thread value of this thread local variable.
1. The underlying implementation of ThreadLocal is to use ThreadLocalMap (it is a static internal class of ThreadLocal, Entry in ThreadLocalMap inherits WeakReference), each thread has a ThredLocalMap, the key value of the element is the current ThreadLocal object, and the value corresponds to the thread A copy of the variable . When calling the get and set methods, the ThreadLocalMap object of the currently running thread is used, so that the private variables of the currently running thread can be obtained and set.
Part of the source code:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
2. When we call the get() method, first get the current thread, then get the ThreadLocalMap object of the current thread, and then get the Entry according to the current ThreadLocal object. If it is not empty, then get the ThreadLocal value, otherwise proceed. Initialization, initialization is to set the value of initialValue to ThreadLocalMap.
Part of the source code:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue(); //null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
3. When we call the set() method, it is very common to set the value into ThreadLocal. Similar to the get() method.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
eg:
public class MyTest {
//一般将ThreadLocal的对象修饰为static对象,方便多个线程共享
public static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Thread t1 = new Thread(new MyTask());
Thread t2 = new Thread(new MyTask());
Thread t3 = new Thread(new MyTask());
//启动3个线程
t1.start();
t2.start();
t3.start();
}
}
class MyTask implements Runnable{
@Override
public void run() {
MyTest.threadLocal.set("0");
//线程计算5次
for(int i=1;i<5;i++){
MyTest.threadLocal.set( MyTest.threadLocal.get().toString()+i);
}
System.out.println("线程"+Thread.currentThread().getName()+"的计算结果:"+MyTest.threadLocal.get());
}
}
operation result:
Calculation result of thread Thread-1: 01234
Thread Thread-0 calculation result: 01234
Calculation result of thread Thread-2: 01234
Doesn't it feel amazing! The three threads all use the same ThreadLocal static variable object to store or obtain values, but the calculation results of the three threads do not affect each other and are independent of each other. That's right, this is the way ThreadLocal solves concurrency. It maintains a thread's private variable for each thread. At the same time, it can be shared by all threads.
ThreadLocalMap source code: During the insertion process,
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;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
According to the hash value of the ThreadLocal object, locate the position i in the table. The process is as follows: 1. If the current position is empty, then just initialize an Entry object and place it at position i; 2. Unfortunately, position i already exists Entry object, if the key of this Entry object happens to be the key to be set, then reset the value in Entry; 3. Unfortunately, the Entry object at position i has nothing to do with the key to be set, so you can only find the next one vacant position;
The length is the same as the length of the hashMap (16), but the entry element in the array is not a linked list.
Entry source code (static internal class of ThreadLocalMap):
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Memory leak problem: After the program applies for memory, it cannot release or use the memory space that has been applied for, and multiple memory leaks will cause memory overflow.
A memory leak may occur. Since the key in the Entry is weakly referenced, if a GC occurs, the key will be recycled. At this time, there will be an Entry with a null key in the ThreadLocalMap, but the entry with a null key cannot be accessed. Value value, if the thread has not ended, the Entry will not be reclaimed and a memory leak will occur.
Solve the memory leak problem:
Calling get() and set() of ThreadLocal may clear the Entry object whose key is null in ThreadLocalMap (if the get method does not find the corresponding Entry, it will reset to ThreadLocal to set a null value, and the set method may change the original Entry Overwrite), so that the corresponding value is not reachable by GC Roots, and can be recycled in the next GC. Of course, if the remove method is called, the corresponding Entry object will definitely be deleted.
How WeakHashMap solves the memory leak problem:
WeakHashMap$Entry:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
/**
* Creates new entry.
*/
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
}//Entry继承WeakReference,所以引用队列中存放的是Entry对象,但是 super(key, queue);所以只有key是弱引用
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
The main solution to the memory leak problem is the expungeStaleEntries method. It will search for the Entry in the ReferenceQueue (the Entry object after the GC is stored), then find the index in the entry array of the WeakHashMap, and then set the value of the relevant Entry from the corresponding chain to null, this completes the cleaning of related data, put, get, remove, size and other methods can trigger the expungeStaleEntries method, but if these methods are not called, memory leaks may occur.