review

Through the study of the previous two notes, we have known how to use Handler,Looper creation and role, MessageQueue creation and role, Message creation and role.

  1. The Handler is created in the main thread, sends the message in the child thread, and sends the message by calling the sendMessage() and post() overloaded methods. When a Message is sent, it carries the time it needs to be processed, and then adds the Message to the appropriate location in the MessageQueue based on the time.

  2. A Looper is created when the main thread is created by calling the prepareMainLooper() method, marking the current thread as a looping thread that cannot exit. The MessageQueue object is created, and the loop() method is started to continuously fetch the Message object from the MessageQueue. The Handler specified by the target property of the MessageQueue is called to handle the Message.

  3. A MessageQueue is a MessageQueue that is created based on time. The constructor in Looper creates a MessageQueue object when it executes. Mainly responsible for the operation of the incoming and outgoing messages.

  4. Message is the entity of the Message, which represents the specific content to be executed. Message maintains an object pool to realize the reuse of Message objects. The recycleUnchecked() method is inserted into the MessageQueue, and the recycleUnchecked() method is inserted into the MessageQueue.

This note mainly studies how to deal with the Message after getting it in Looper.

Handler.dispatchMessage(Message)Methods the source code

In the first paper notes, we have learned, in which the loop () method is performed in the loop for the next Message, through MSG. Target. DispatchMessage (MSG) to distribute the Message to the corresponding Handler, Here is the source code for this method:

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if(msg.callback ! = null) { handleCallback(msg); }else {
        if(mCallback ! = null) {if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}Copy the code
  1. In this method, the current is judged firstMessagethecallbackWhether it is empty or not, we can see from the last note,MessagethecallbackIs of typeRunnableThrough theHandler.post(Runnable)The series constructor sends a message toMessagethecallbackAttribute assignment, source code as follows:
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
Copy the code
  1. ifMessagethecallbackProperty is not empty, it executeshandCallback(Message)Method, source as follows:
private static void handleCallback(Message message) {
    message.callback.run();
}
Copy the code

As you can see from the source code, this is just the run() method of the Runnable that corresponds to the callback property of Message.

  1. ifMessagethecallbackProperty is empty, which means it does not passpost()A series of overloaded methods sent messages, then the next judgmentHandlerIn themCallbackProperty is null.

MCallback is of type handler. Callback, which is an internal interface to the Handler that also handles Message messages. Final Callback mCallback by looking at the definition of mCallback; You also know that this property can only be assigned by the constructor. So there is another way to define a Handler:

private Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        if(msg.what == 0){
            mBinding.tvResult.setText(msg.obj.toString());
        }
        return false; }});Copy the code

This way we don’t need to create a Handler using an inner class, which can cause memory leaks.

  1. If the value of the mCallback property is not empty, then the Message will be processed through this interface. As you can see, note that if the Message was successfully processed, it will return true. The default implementation of this method returns false. Because if the processing succeeds and returns false, then the following handleMessage(MSG) method continues, which is unnecessary.

  2. If the mCallback property is null, then the handleMessage(Message) method is called to handle the Message. This method usually requires us to override its implementation, which is null by default in the source code:

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}
Copy the code

At this point, the Handler’s entire process is finished, as shown in the figure below:

Handler Indicates the overall execution process

aboutMessageAdd the barrier stack

Is mentioned in the previous notes, there is a target for empty Message is called the stack, but our Message is sent via a Handler for the target assignment, the barrier in the MessageQueue stack is, how to add the source code is as follows:

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when ! = 0) { while (p ! = null && p.when <= when) { prev = p; p = p.next; } } if (prev ! = null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; }}Copy the code

In the source code above, postSyncBarrier() and its overloading function add a stack. The way to add a stack is not very different from the way to add a Message. The only difference is that you don’t have to worry about the stack, because what you’re adding is a stack. So it just needs to be inserted into the queue at the appropriate location depending on its execution time factor.

ThreadLocal

In previous notes, we learned that a thread can only have one Looper. This is because when we create a Looper object, we save it to a ThreadLocal. If we need to create another Looper object, If there is a value in ThreadLocal, the current thread has already created a Looper, and an exception is thrown.

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
    if(sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
Copy the code

If there is a Looper object, an exception will be thrown. If there is no Looper object, a private constructor will be called to create a Looper object, and then the Looper object will be set to the ThreadLocal.

So the source code to see here, there will be the following problems to solve.

ThreadLocalWhat is? What does it do?

The ThreadLocal class provides thread-local variables, which, unlike ordinary variables, are individually initialized copies of which each thread can access through its get or set methods. ThreadLocal instance variables are typically privte static fields in a class that want to associate state with a thread, such as a user ID or a transaction ID.

A simple example is this:

private ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
private Integer local2 = Integer.valueOf(0);

@Override
protected void initUi() {
    threadLocal1.set(100);
    local2 = 200;
    Log.e("TAG"."Data in main thread :"+threadLocal1.get()+","+local2);
}

@Override
public void doClick(View view) {
    super.doClick(view);
    if(view.getId() == R.id.btn_get_value){
        Log.e("TAG"."Data in main thread :"+threadLocal1.get()+","+local2);
        return;
    }
    new Thread(new Runnable() {
        @Override
        public void run() {
            threadLocal1.set(400);
            local2 = 500;
            Log.e("TAG"."Data in child thread 1 :"+threadLocal1.get()+","+local2);
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.e("TAG"."Data in child thread 2 :"+threadLocal1.get()+","+local2);
        }
    }).start();
}
Copy the code

In the above code, we first define a ThreadLocal

variable and a normal Integer variable in the main thread, then set the values of these two variables to 100 and 200 in the main thread, respectively, and start both threads at the click event. Thread 1 sets the values of these two variables to 400 and 500, respectively. Thread 2 sets no values and simply prints the data. When another button is clicked, the data is printed in the main thread again. The final print is as follows:

The 2020-03-03 09:59:14. 097, 4059-4059 / com. Example. Myapplication E/TAG: The data in the main thread: 100200 2020-03-03 09:59:16. 150, 4059-4151 / com. Example. Myapplication E/TAG: The data in the child thread 1:400500 2020-03-03 09:59:16. 155, 4059-4152 / com. Example. Myapplication E/TAG: Data in a child thread 2: null, 500 the 2020-03-03 09:59:18. 955, 4059-4059 / com. Example. Myapplication E/TAG: the data in the main thread: 100500Copy the code

As you can see from the data printed above, the value of using a ThreadLocal variable depends on the thread, in which thread what value is set, and only in that thread can it be obtained. Changing the value of a variable in another thread does not affect the value set in the other thread. However, a common variable can be modified by any thread.

Another way is what happens when we pass a ThreadLocal variable?

class ThreadTest3 extends Thread{

    private ThreadLocal threadLocal;
    ThreadTest3(ThreadLocal threadLocal){
        this.threadLocal = threadLocal;
    }
    @Override
    public void run() {
        super.run();
        Log.e("TAG"."Data in thread 3 :"+threadLocal.get()); }}Copy the code

The above code runs as follows:

The 2020-03-03 10:05:44. 042, 4261-4261 / com. Example. Myapplication E/TAG: The data in the main thread: 100200 2020-03-03 10:05:46. 747, 4261-4338 / com. Example. Myapplication E/TAG: thread data in 3: nullCopy the code

As you can see, even if we pass a ThreadLocal to another thread, the data set in the previous thread will not appear in the new thread.

As you can see from the example above, the purpose of a ThreadLocal is to provide a value that is independent of the current thread. This value is not visible to other threads, so they cannot use or modify the value of the current thread.

Back to Looper, we have learned from the previous notes that Looper does not exist only in the main thread. We can also use Looper to create our own message loop mechanism in other threads. In other words, a Looper is bound to a thread. The main thread owns the Looper of the main thread, and the child thread owns the Looper of the child thread. The Looper of the main thread and the child thread cannot affect each other. Thus, Looper of different threads will not affect each other.

Relevant variables

The following variables are defined in ThreadLocal, and you can see what they do with the comments:

The variable name role
threadLocalHashCode This variable is decorated with final int and assigned by the static nextHashCode() method. The value of this parameter is initialized when the class initializes, since the value of a ThreadLocal is stored in a Map structure where the key of the Map is the ThreadLocal object and the value is the value we want to hold. This value can be thought of as representing the object that was initialized, and is then used to perform operations on the Map
nextHashCode This is a static variable that generates the next hash code to use, atomic update, starting at 0. The nextHashCode() method internally generates the next threadLocalHashCode to use by adding this value to a fixed hexadecimal number
HASH_INCREMENT This is a constant with a value of 0x61C88647 and represents the increment of the hash code generated each time. The nextHashCode value is added to this number in the nextHashCode() method to generate the next threadLocalHashCode to be used

These are the variables used. Here are some related methods to learn.

set(T)

When we initialize a ThreadLocal, we assign to it via the set() method. Here is the source code for the set(T) 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);
    }
Copy the code

The comment in this method is as follows: sets the current thread copy of this thread local variable to the specified value. Most subclasses will not need to override this method and will simply rely on the initialValue() method to set the value of thread-local variables.

In general, if we know the value stored in a ThreadLocal, we can specify the value by overriding the initialValue() method. But sometimes we don’t know what the value of the initialization is, and it can be specified using this method.

GetMap (t) getMap(t) getMap(t) getMap(t) getMap(t) getMap(t) getMap(t) getMap(t) getMap(t) getMap(t) getMap(t)

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
Copy the code

We can see that we get the threadLocals object in Thread directly:

/ / Thread class ThreadLocal ThreadLocalMap threadLocals = null;Copy the code

If the obtained ThreadLocalMap is empty, then createMap(t,value) is executed to create a ThareadLocalMap:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code

In this method, we create a ThreadLocalMap object and pass the values we want to hold in the constructor. Here is the constructor of a ThreadLocalMap:

ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1;setThreshold(INITIAL_CAPACITY);
}
Copy the code

This is where you start with the ThreadLoacalMap class. Let’s take a look at the class as a whole. The documentation for this class is as follows:

ThreadLocalMap is a custom hash map that only works for maintaining thread-local values. No operations are exported outside the ThreadLocal class. This class is package private to allow the declaration of fields in the Thread class. To help deal with the very long service life, hash table entries use WeakReference as the key. However, because reference queues are not used, obsolete entries are only guaranteed to be dropped when the table space is insufficient.

Within this class, an Entry class is defined to hold the data. Comments about this class are as follows:

The entry in this hash map uses its primary reference field as a key (always a ThreadLocal object), extending WeakReference. Note that an empty key (that is, entry.get() = NULL) means that the key is no longer referenced (as you can see from the expansion method), so the entry can be removed from the table. In the following code, such entries are called “stale entries.”

Related attributes are as follows:

The property name role
INITIAL_CAPACITY This is a constant with a value of 16, and the comments indicate that this value must be a power of 2
table This is an array of entries that can be resized as needed; length must be a power of 2
size Number of entries in the table
threshold The next size value to resize, which defaults to 0

ThreadLoalMap: ThreadLocalMap: ThreadLocalMap: ThreadLocalMap: ThreadLocalMap

ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1;setThreshold(INITIAL_CAPACITY);
}
Copy the code

As you can see, the following is done in the constructor:

  1. createEntryArray of length 16
  2. Get what’s passed inThreadLocalThe object of thethreadLocalHashCodeOf, and at the same time15To figure out where the current data should be inserted.
  3. Create a new oneEntryClass,ThreadLocal(key) and the value to savefirstValue(value), inserted into the position calculated in the previous step.
  4. Set up thesizeProperty has a value of 1, indicating that a value has been inserted into the current array
  5. callsetThreshold(16)To determine the size of the next increment, this method source code is as follows:
/**
 * Set the resize threshold to maintain at worst a 2/3 load factor.
 */
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}
Copy the code

As you can see, we do an *2/3 operation on the passed parameter and assign the value to the threshold attribute.

At this point, the process ends when we first add data to ThreadLocalMap. The whole process is relatively simple.

set(T)– ThreadLocalMap isn’t empty

When setting data, we already know that when the ThreadLocalMap of the current thread is empty, we create a ThreadLocalMap object to hold the data that needs to be saved.

If ThreadLocalMap is not empty, map.set(this,value) is called directly to set the data. Here is the source code for this method:

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(); }Copy the code

The source code for this method is commented as follows:

We don’t use fast paths like get(), because there is at least one case where set() is used to create an entry and replace an existing one, and the probability of a fast path failing is higher in that case.

So there’s a question, how does get() get the path, and what’s the difference between set()? Make a note of this and think about it when you look at the get() source code.

In the above source code, the operation is as follows:

  1. Calculate where the entry to be created or modified is located. The calculation method is the same as beforeThreadLocalIn thethreadLocalHashCodeValues andThe length of the array is -1The initial size of the array is 2 to the NTH power, and the size of the array must be 2 to the NTH power.
  2. And then we go to oneforLoop to get the position calculated in the previous stepEntryObject, and then determine whether it is empty, if it is empty directly out of the loop, the current need to set the data directly createdEntryObject to the specified position in the array. If it is not empty, it determines this one at the current positionEntryThe object’skeyWhether or not to be set/modifiedkeyYes, if yes, modify it directlyEntryIn thevalueIf they are different, they passnextIndex(i,len)To get the next positionEntry, until it ends or finds something that satisfies the conditionEntry. The following isnextIndex(i,len)Method source:
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}
Copy the code

As you can see from the source code above, we simply determine whether the next array index is out of bounds. If it is out of bounds, we start with the index 0. If not, we use the current index.

Here is to do so, may have a conflict is mainly because the hash code, in order to solve the conflict, the linear probe method is used here, that is to say, I need to insert a data, but found that the position of the insertion already has data (hash conflict), and the location of the data and I want to insert the data of the key is not the same, So I’m going to find the next place to insert or modify. Together before the source, in fact, we can know that the array of the initial length of 16, assignment to take up a position for the first time, so every time behind the set values can always find the location of the empty, each position of inserting data into the air after the operation, to determine whether array need to increase, if need to increase will go to expand the size of the array, So that there is no place for the element to be inserted.

  1. If you get theEntryIf it is not empty, then judge the obtainedEntrythekeyAnd the currently specifiedkeyWhether the same, the same words directly modifyEntryIn thevalueField and return.
  2. If you find thatkeyNo, then judgekeyIs it empty or not? If it is empty, it is calledreplaceStaleEntry(key,value,i)To replace the currentEntryIn thekeyThe correspondingvalueValue and return. The following isreplaceStaleEntry(key,value,i)Source:
private void replaceStaleEntry(ThreadLocal<? > key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to checkfor prior stale entry in current run.
    // We clean out whole runs at a time to avoid continual
    // incremental rehashing due to garbage collector freeing
    // up refs in bunches (i.e., whenever the collector runs).
    int slotToExpunge = staleSlot;
    for(int i = prevIndex(staleSlot, len); (e = tab[i]) ! = null; i = prevIndex(i, len))if (e.get() == null)
            slotToExpunge = i;
    // Find either the key or trailing null slot of run, whichever
    // occurs first
    for(int i = nextIndex(staleSlot, len); (e = tab[i]) ! = null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get(); // If we find key,then we need to swap it
        // with the stale entry to maintain hash table order.
        // The newly stale slot, or any other stale slot
        // encountered above it, can then be sent to expungeStaleEntry
        // to remove or rehash all of the other entries in run.
        if (k == key) {
            e.value = value;
                tab[i] = tab[staleSlot];
            tab[staleSlot] = e;
            // Start expunge at preceding stale entry if it exists
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }
        // If we did not find stale entry on backward scan, the
        // first stale entry seen while scanning for key is the
        // first still present in the run.
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }
    // If key not found, put new entry in stale slot
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);
    // If there are any other stale entries in run, expunge them
    if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }Copy the code

In this method, you first set the slotToExpunge variable to the position you are replacing (to be clear, slotToExpunge is the location you want to clean up), then start from the position before that and look forward to see if there are any other objects with null Entry keys. If so, record it. Since this is a circular array, the loop will either stop when the first Entry is empty, or stop at the beginning of the loop after a full circle. Thus, the slotToExpunge variable actually holds the index of another Entry (or the same Entry) whose key is null.

To start the next cycle, first consider a situation: At first, I wanted to save an Entry in table 10 of the array, but unfortunately, due to hash conflict, the Entry in table 10 of the array was not empty, so I had no choice but to search for it. After searching for a while, I found that the position in table 13 of the array was empty and had no data. At this point, I have saved the data to be saved in the array at index 13. After saving, after a certain period of time, the key in the Entry at table 10 under the array is cleared and becomes null. So wait for me next time before you want to save the data is modified, the hash computed position or 10, but the Entry on the 10 key is empty, so I just from 10 this position to look behind and see if I can find the data I want to change, finally found in will certainly in the position of 13, after finding, I changed the numbers. Then I swap the data at position 13 with the data at position 10, so that the next time I want to change the data I saved, I can hash it to 10, and just change it without having to go all the way back. So, what we’re going to clean up at this point is the data at index 13. The second loop does this in the first place, corresponding to the following source code:

for(int i = nextIndex(staleSlot, len); (e = tab[i]) ! = null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get(); // If we find key,then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;
Copy the code

SlotToExpunge holds the index of the position to be cleaned, and staleSlot is the index of the position to be cleaned that was passed in at the beginning. After the first loop, if slotToExpunge is the same as staleSlot, That means that this is the position that needs to be cleaned up, but since staleSlot and I may have switched positions after the second loop, as mentioned above, in this case, the data that needs to be cleaned up is at I, so I’m going to set slotToExpunge to I, And execute cleanSomeSlots(expungeStaleEntry(slotToExpunge), len) method, first is the source of the expungeStaleEntry(slotToExpunge) method as follows:

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;
    // Rehash until we encounter 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;
}
Copy the code

According to the annotation of this method, the function of this method is to clean the data in the current position, and check whether the hash value of the current position corresponds to the next non-empty position, and if not, determine whether the data in the corresponding position needs to be exchanged. Let’s take an example where we have a ThreadLocal that needs to be stored at a hash value of 2, and we store that ThreadLocal at a hash value of 2 in the array, and then another ThreadLocal is created. The calculated position is 3(in fact, the probability of this happening is relatively small, but this is just for illustration). We’ll save this ThreadLocal at position 3, and then we’ll create another ThreadLocal at position 2, and we’ll have a hash collision, so we already know that this ThreadLocal will be stored at position 4. Then another ThreadLocal will appear, and it will be calculated at position 3. Because of hash collisions, we know that this ThreadLocal will be stored at position 5. After some time, the ThreadLocal at position 3 is cleared, causing the key of the Entry at position 3 to become null. At this point, we want to change the data at position 5. This data is hash to position 3, but because the ThreadLocal at position 3 is cleared, As a result, the key will be empty. According to the previous code, it will start from here to look for the next position where the key is the same or the position is empty, and then it will find 5. After finding 5, it will change the data. So now I have data at position 3, and I have no data at position 5. Once you’ve done that, you’re going to start iterating from position 3, and you’re going to keep iterating until the next position is empty, and in this case you’re going to stop at position 5. The reason for this traversal is that the hash conflict occurs at positions 2 and 4, and the key at position 2 May also be cleared, so you need to set the data at position 4 to position 2. (Note that if the key at position 4 is cleared, then the data at position 4 is set to null. If the key at position 2 is not cleared, then the data at position 2 will be searched for the next position, and then the data at position 4 will not change.)

I understand why this is so complicated, but it’s probably because the probability of occurrence in the two examples mentioned above is relatively small. The number of hashes used to calculate the hash is a perfect hash in many cases, so take all of these factors into account whenever there is a hash conflict. Because the next hash conflict is unknown when, avoid the dirty data can not be recycled. Another thing THAT I personally think might be because the get() method, which we mentioned earlier, is much more convenient to get data than the set() method, so it takes a little bit less logic to do this. But this is my personal guess, not necessarily accurate.

This method will eventually return the location next to the location we changed, or by default, the location next to the location we swapped in the previous step, if no data has been changed.

Go to the cleanSomeSlots(int I, int n) method source code:

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        if(e ! = null && e.get() == null) { n = len; removed =true; i = expungeStaleEntry(i); }}while( (n >>>= 1) ! = 0);return removed;
}
Copy the code

Judging from the comment on this method, this method uses a heuristic to scan certain units for stale entries and is called when a new element is added or another old element is removed. The execution is still done by traversing the history to see if the key(ThreadLocal) of the Entry at the current location is empty. If it is empty, the deletion operation is still performed by calling expungeStaleEntry(I), which we just analyzed. Note that the loop condition uses unsigned right shift. If the length of the array is 16, the unsigned right shift will cause the loop to execute 4 times. while… If the binary number of 15 is 1111 and the unsigned right shift is 0111,0011,0001,0000, that is, 15, 7, 3, and 1 will be executed once.

The heuristic scan is because if an empty key is found, the value of n will be reset, resulting in an increase in the number of loops. The worst case is that the entire array needs to be scanned, so the comment also says that this method may cause the set(T) to be O(n) at some point. Note that this is my personal understanding, I did not find the specific meaning of the heuristic scan, based on the source code execution flow generated such understanding, do not know if it is correct.

Mentioned above are our expectations can behind key = = null to this place to find we want to insert the key/modify the data, but in fact most of the time we do not know, also because rarely appear hash conflict, from the perspective of the implementation process of the above, several times to traverse, this also leads to high time complexity, So the above methods should be used as little as possible.

If we do not find the key we want to set/modify, we will remove the Entry from the current location and set the new data on it, corresponding to the following operation in the source code:

  // If key not found, put new entry in stale slot
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);
Copy the code

The replaceStaleEntry method starts with the current empty key position and looks forward to see if there are any other empty key positions before this position. If this position is found, the data with empty keys is cleared from this position. This corresponds to the following operation in the source code:

// If there are any other stale entries in run, expunge them
if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);Copy the code

We need to note that the array will be expanded according to the situation, so there will not be data in the data store full situation, so the previous traversal must encounter a certain position data empty and stop, there will not be an infinite loop situation.

At this point, we are done judging when the key of the Entry at the location we want to set/modify is empty.

  1. Program execution to this point, the current array is not found to set/modifyEntry, then create a new oneEntrysavekeyandvalue.
  2. Finally, if we haven’t cleaned up the data and the existing data is greater than the threshold we set earlier, then go aheadrehash()In the operation.
if(! cleanSomeSlots(i, sz) && sz >= threshold)rehash(a);Copy the code
private void rehash() {
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}
Copy the code

As you can see, the expungeStaleEntries() method was first used, and the source code is as follows:

private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if (e != null && e.get() == null)
            expungeStaleEntry(j);
    }
}
Copy the code

In this method, we iterate through the array to clean up those elements whose key is == null.

After clearing the data, judge whether the existing data is greater than or equal to threshold-threshold / 4. If the value is greater than or equal to this value, then call the resize() method to expand the capacity. The source code is as follows:

private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;

    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if(e ! = null) { ThreadLocal<? > k = e.get();if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while(newTab[h] ! = null) h = nextIndex(h, newLen); newTab[h] = e; count++; }}}setThreshold(newLen);
    size = count;
    table = newTab;
}
Copy the code

As you can see, the expansion method is actually very simple, mainly do the following operations:

  1. Set the size of the new array to twice the size of the original array. Since the array was originally 16 in length, each expansion ensures that the array is 2 to the n.
  2. Iterating through the original array, determining each positionEntrythekey(ThreadLocal)Whether to set this parameter to null. If yes, set this parametervalueAlso for the empty to helpGC.
  3. If the value is not null, it passesthreadLocalHashCodeCalculate the position, and if the position is in conflict, loop to calculate the next position that can be inserted, and then save the data there.
  4. Set the expansion threshold, the amount of data that has been filled, and the array variable pointing to the new array. The capacity expansion is complete.

At this point, the set(T) method is analyzed. After the analysis of this method is completed, you can find that most of the methods inside are analyzed.

T get()

The source code for the get() method is as follows:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this);if(e ! = null) { @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}return setInitialValue();
}
Copy the code

If the ThreadLocalMap is not empty, then we call map.getentry (this) to retrieve the data and return. Otherwise, call the setInitialValue() method.

map.getEntry(this)

First look at the source code for the map.getentry (this) method:

private Entry getEntry(ThreadLocal<? > key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i];if(e ! = null && e.get() == key)return e;
    else
        return getEntryAfterMiss(key, i, e);
}
Copy the code

The logic is simple. We first calculate the location using the threadLocalHashCode value, and then return the Entry if the threadLocalMap. Entry at that location is not empty and has the same key. Otherwise call getEntryAfterMiss(ThreadLocal
key, int I, Entry 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)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
Copy the code

The logic is very simple, that is, through the loop to determine whether the array of Entry key is the same as the need to return, if there is, return null. The expungeStaleEntry(I) method is still called if the key == null is found during the traversal.

setInitialValue()

This method is called if the obtained ThreadLocalMap is empty or if the current ThreadLocal value is not stored in the ThreadLocalMap.

private T setInitialValue() {
    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

As you can see, this method first calls initialValue() to get the initialValue, and then executes the same logic as the set(T) method. No further details.

remove()methods

The logic for the remove() method is also relatively simple, still getting the ThreadLocalMap and calling the remove(ThreadLocal
t) method delete data, source:

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if(m ! = null) m.remove(this); }Copy the code
private void remove(ThreadLocal<? > key) { 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)]) {if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return; }}}Copy the code

As you can see, the position is still calculated using threadLocalHashCode, then traversed to see if the keys are equal, if so, the object is cleared with a call to clear(), followed by a call to expungeStaleEntry(I) to clear the position.

It should be noted that ThreadLocalMap.Entry is inherited from WeakReference. The above clear() method internally calls the local clearReferent() method to clean up the reference. The annotation of this method is as follows: Disallow direct access to the reference object and, as long as it is safe to do so, clear the object block and set the reference to NULL.

That concludes the source code for ThreadLocal.