当前位置:网站首页>Analysis of ThreadLocal principle

Analysis of ThreadLocal principle

2020-11-06 01:15:00 Stack

What about today , Let's talk to you ThreadLocal.

1. What is it? ?

JDK1.2 Provides a thread bound variable class .

His idea is : Clone each thread that uses this resource , Different threads use different resources , And the resources are independent of each other

2. Why ?

Think about a scene : When the database is connected , We will create a Connection Connect , Let different threads use . At this time, there will be multiple threads competing for the same resource .

In this case, multiple threads are competing for the same resource , Very common , There are only two common solutions : Space for time , Time for space

No way out , You can't have both . Just like our CAP theory , And one of the sacrifices , Guarantee the other two .

For the above scenario, our solution is as follows :

  • Space for time : Create a connection for each thread .
    • Directly in thread work , Create a connection .( Too much duplicate code )
    • Use ThreadLocal, Bind a connection for each thread .
  • Time for space : Lock the current resource , Only one thread at a time can use this connection .

adopt ThreadLocal Bind a variable of the specified type for each thread , This is equivalent to thread privatization

3. How to use it? ?

ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.get();
threadLocal.set(1);
threadLocal.remove();

you 're right , These four lines of code have put ThreadLocal How to use it is very clear .

  • get from ThreadLocal Take out an object owned by the current thread
  • set Bind an object to the current thread
  • remove Remove the current binding from the current thread

Remember to use after , must do remove, must do remove, must do remove

Why remove. I believe many friends have heard of ThreadLocal Can cause memory leak problem .

you 're right , So in order to solve this situation , So you know , Remove after use , Don't waste space ( Slag man is pleased )

See this , There are a lot of question marks on my head ( Children, do you have many question marks ?

Why can cause memory leak ?

Why not remove There's a memory leak

How does it say object and thread binding

Why? get What you get is the current thread's, not the other thread's

How does it work ???

Come on , Kaigan , Source code

4. Source code interpretation

Let's start with an idea : If we write our own ThreadLocal How to write ?

Thread binds an object . Isn't this what we all know map mapping ? With Map We can use threads as Key, The object is value Add to a collection , And then all kinds of get,set,remove operation , Play as you like , Get it done .

This is the time , A brother said it . You don't have the right idea , You can only store one type of variable in a thread , I want to save more than one ?

Feel your full hair , You've come up with a great saying : All kinds of problems , It's all about the source and the result .

In terms of the result , Let developers make their own thread private ( It is estimated that the developers will scold me to death )

Come on , Consider from the source . Now what we need is : Threads can bind multiple values , Not just one . Um. , you 're right , Brothers, say what you think .

Let the thread maintain its own Map, Put this ThreadLocal As Key, Object as Value No, it's done

brother , Four points of view


here , Another brother said . In your way , take ThreadLocal Throw it into the thread itself Map in , That's not this ThreadLocal Always referenced by thread objects , So it is reachable until the thread is destroyed , No way GC ah , Yes BUG ah ???

good , problem . Think so , Since threads and ThreadLocal Object has reference , Cause to be unable to GC, Then if I make the reference between you and thread into weak reference or soft reference, it will become . One GC You're gone .

what , You don't know what weak and soft references are ???

What I said earlier , Come on, let's review another wave of .

JDK There are four types of references in , The default is strong reference , That's what we do all the time . insane new,new,new. The objects created at this time are strong references .

  • Strong citation . direct new
  • Soft citation . adopt SoftReference establish , Destroy directly when the memory space is insufficient , That is, it may be the last place to be destroyed in the old age area
  • Weak reference . adopt WeakReference establish , stay GC And destroy it directly . That is, the place of destruction must be the District of Eden
  • Virtual reference . adopt PhantomReference establish , It's the same as not saving , Very empty , You can only do some operations through the reference queue , It is mainly used for out of heap memory recycling

Okay , Back to the point , The most suitable reference for our current scenario is weak reference , Why do you say :

In the past, we wait after we've used the object GC clear , But for ThreadLocal Come on , Even if we use the end , Also because the thread itself has a reference to the object , In the reachable state of an object , The garbage collector can't recycle . At this time ThreadLocal Too many times there will be a memory leak .

And we will ThreadLocal Object as a weak reference , So it's a good solution to this problem . When we use it ourselves ThreadLocal in the future , When GC The strong references that we created will be directly killed when the , At this time, we can put the thread Map Get rid of the quotation in , So we use weak references , At this time, we should understand why we don't use soft references

There's another problem : Why does it cause a memory leak ?

understand Map The brothers of structure should be aware of , The interior is actually an array of nodes , about ThreadLocalMap for , Inside is a Entity, It will Key As a weak reference ,Value Or strong reference . If we finish using ThreadLocal in the future , No, right Entity Remove , Can cause memory leaks .

ThreadLocalMap Provides a way expungeStaleEntry Method is used to exclude invalid EntityKey Empty entities )

Speaking of this , There is a problem that I have been thinking about for a long time ,value Why not make a weak reference , How good it would be to throw it away after use

Finally, I think about the answer ( Push according to the source code ):

Not set to weak reference , Because I don't know this Value except map Whether or not there are other references , If there are no other references , When GC I will directly put this Value It's gone , And now our ThreadLocal It's still in use , Will cause Value by null Error of , So set it to a strong reference .

And to solve the problem of strong references , It provides a mechanism that will Key by Null Of Entity Direct removal

Come here , The design of this class is already clear . Let's take a look at the source code !


One thing to note is :ThreadLocalMap The way to solve hash conflict is linear detection .

Human words are : If the current array bit has a value , Then judge whether the next array bit has a value , If there is value, keep looking down , Until an empty array bit

Set Method

class ThreadLocal	
	public void set(T value) {
    	// Get the current thread 
        Thread t = Thread.currentThread();
    // Gets the current thread's ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // If the current thread's Map Created , direct set
            map.set(this, value);
        else
            // Not created , Create Map
            createMap(t, value);
    }

	private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
			// Get the current array bit , Whether the current array bit is bit null, If null, Direct assignment , If not for null, Then linear search for a null, assignment 
            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;
        // Remove some of the invalid Entity
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }


	ThreadLocalMap getMap(Thread t) {
    // Gets the current thread's ThreadLocalMap
        return t.threadLocals;
    }

	void createMap(Thread t, T firstValue) {
        	// The current object is used as Key, Just like we thought 
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

Get Method

	public T get() {
        // Get the current thread 
        Thread t = Thread.currentThread();
        // Get the current thread's Map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // Get this entity 
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                // return 
                return result;
            }
        }
        return setInitialValue();
    }

	private Entry getEntry(ThreadLocal<?> key) {
        // Calculate array bits 
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
        // If the current array has values , And array bit key identical , Then return to value
            if (e != null && e.get() == key)
                return e;
            else
                // Linear detection looks for the corresponding Key
                return getEntryAfterMiss(key, i, e);
        }

	private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    // Exclude the currently empty Entity
                    expungeStaleEntry(i);
                else
                    // Get the next array bit 
                    i = nextIndex(i, len);
                e = tab[i];
            }
        // If it is not found, it will return empty 
            return null;
        }

remove

	public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

	private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
        // Get the current array , Determine whether it is the required array bit , If it's not a linear search 
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    // Clear the vacancy NUll Entity of 
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

We can see a phenomenon : stay set,get,remove When it's time to call expungeStaleEntry To take all the invalid Entity remove

Take a look at what this method does

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            //  Delete entity's Value
            tab[staleSlot].value = null;
    // Empty this array bit 
            tab[staleSlot] = null;
    // Quantity minus one 
            size--;

            //  Recalculate the hash again , If the current array bit is not null, Linear search until a null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

More original content, please pay attention to official account number @MakerStack

版权声明
本文为[Stack]所创,转载请带上原文链接,感谢