Introduction

ThreadLocal in JDK can obtain the value bound to the current thread through the get method. And these values are stored in ThreadLocal.ThreadLocalMap. The underlying data storage in ThreadLocalMap is in an Entry array.

So how fast is it to get data from ThreadLocalMap? Is there any room for optimization in speed?

Take a look.

Get data from ThreadLocalMap

ThreadLocalMap is used as a Map, and its underlying data storage is an array of Entry type:

 private Entry[] table;

Let's review how ThreadLocal obtains data:

 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);
        }

First, perform a modulo operation on the length of the table based on the threadLocalHashCode in the ThreadLocal object to obtain the position of the Entry to be obtained in the table, and then determine whether the key of the position Entry is consistent with the ThreadLocal object to be obtained.

If it is consistent, it means that the object bound by ThreadLocal has been obtained, and it can be returned directly.

If not, you need to do the lookup again.

Let's look at the logic of searching again:

 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;
        }

The logic of getEntryAfterMiss is to first determine whether the object in the Entry is the object to be obtained, and if so, return it directly.

If the object in the Entry is empty, the method to clear the expired Entry is triggered. Otherwise, calculate the next address to be judged, and judge again until the object to be found is finally found.

It can be seen that if the object to be found is not found the first time, it may be traversed many times later, resulting in lower execution efficiency.

So is there any way to improve the speed of this search? The answer is yes.

FastThreadLocal

As we mentioned before, the local object pool technology in Netty, netty created a special class called Recycler for it. Although ThreadLocal is also used in Recycler, the threadLocal used by Recycler is not ThreadLocal that comes with JDK, but FastThreadLocal. The ThreadLocalMap associated with it is called InternalThreadLocalMap, and the Thread associated with it is called FastThreadLocalThread. The correspondence between classes in netty and classes in JDK is as follows:

objects in netty Objects in JDK
FastThreadLocalThread Thread
InternalThreadLocalMap ThreadLocal.ThreadLocalMap
FastThreadLocal ThreadLocal

Let's first look at FastThreadLocalThread. Regardless of whether it is fast or not, since it is a Thread, it will naturally inherit from JDK's Thread:

 public class FastThreadLocalThread extends Thread

Like Thread, FastThreadLocalThread also has a ThreadLocalMap called InternalThreadLocalMap, which is a private attribute of FastThreadLocalThread:

 private InternalThreadLocalMap threadLocalMap;

There is also a ThreadLocal object in InternalThreadLocalMap, called slowThreadLocalMap, which is used when fastThreadLocalMap does not take effect.

Next, let's take a look at why this ThreadLocalMap is fast:

 public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            return fastGet((FastThreadLocalThread) thread);
        } else {
            return slowGet();
        }
    }

As can be seen from the get method, if the current thread is FastThreadLocalThread, the fastGet method will be called, otherwise the slowGet method will be called.

The slowGet method is to use the traditional ThreadLocal to get:

 private static InternalThreadLocalMap slowGet() {
        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        if (ret == null) {
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }

We focus on the fastGet method:

 private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

Here the effect of fast appears, fastGet directly returns the InternalThreadLocalMap object in the thread, without any search process.

Let's see how FastThreadLocal uses the get method to get specific values:

 public final V get() {
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        Object v = threadLocalMap.indexedVariable(index);
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }

        return initialize(threadLocalMap);
    }

It can be seen that the get in FastThreadLocal first calls the get method of InternalThreadLocalMap and directly returns the InternalThreadLocalMap object in FastThreadLocalThread, which is very fast.

Then directly use the index in FastThreadLocal to get the elements in the array that specifically stores data in threadLocalMap:

 public Object indexedVariable(int index) {
        Object[] lookup = indexedVariables;
        return index < lookup.length? lookup[index] : UNSET;
    }

Because it is accessed directly by index, it is also very fast. This is the origin of fast.

Then there is the question of the classmates' meeting. How does the index in FastThreadLocal come from?

 private final int index;

    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }

And the nextVariableIndex method in InternalThreadLocalMap is a static method:

 public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }

That is to say, as long as a new FastThreadLocal is new, a unique index will be generated in the object. Then FastThreadLocal uses the index to access objects in InternalThreadLocalMap. In this way, there is no need for multiple traversal and search like ThreadLocal.

Summarize

FastThreadLocal is only really fast when used in conjunction with FastThreadLocalThread, otherwise it will fallback to ThreadLocal for execution. You must pay attention to this.

For more information, please refer to http://www.flydean.com/48-netty-fastthreadlocal/

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com