[9] The thread copy ThreadLocal, and WeakHashMap

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.

Guess you like

Origin blog.csdn.net/Jack_PJ/article/details/88016471