Introduction

Thread in JDK must be used by everyone, as long as students who have used asynchronous programming must be familiar with it. In order to save the unique variables in Thread, JDK introduced the ThreadLocal class to specifically manage the local variables of Thread.

ThreadLocal

Many newcomers may not understand what ThreadLocal is and what it has to do with Thread.

In fact, it is very simple. ThreadLocal is essentially a key, and its value is the value stored in a map in Thread.

There is a Map in each Thread, and the type of this Map is ThreadLocal.ThreadLocalMap. Let's not specifically discuss how this ThreadLocalMap is implemented. Simply think of it as a map for now.

Next, let's look at the workflow of the next ThreadLocal.

Let's first look at an example of the use of ThreadLocal:

 public class ThreadId {
       // 一个线程ID的自增器
       private static final AtomicInteger nextId = new AtomicInteger(0);
  
       // 为每个Thread分配一个线程
       private static final ThreadLocal<Integer> threadId =
           new ThreadLocal<Integer>() {
               @Override protected Integer initialValue() {
                   return nextId.getAndIncrement();
           }
       };
  
       // 返回当前线程的ID
       public static int get() {
           return threadId.get();
       }
   }

What is the purpose of the above class?

When you call the get method of ThreadId in different thread environments, different int values will be returned. So it can be seen that ThreadId generates a thread ID for each thread.

Let's see how it works.

First we call the get method of ThreadLocal<Integer>. The get method in ThreadLocal is defined 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;
                return result;
            }
        }
        return setInitialValue();
    }

In the get method, we first get the current thread Thread, and then getMap returns the ThreadLocalMap object in the current Thread.

If the Map is not empty, take out the value corresponding to the current ThreadLocal as the key.

If the Map is empty, the initialization method is called:

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

The initialization method first determines whether the ThreadLocalMap in the current Thread is empty, and if not, sets the initialized value.

Create a new Map if empty:

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

You can see that the Key in the entire ThreadLocalMap is the ThreadLocal itself, and the Value is the generic value defined in the ThreadLocal.

Now let's summarize what ThreadLocal does.

There is a Map object of ThreadLocal.ThreadLocalMap in each Thread. We want to store some specific values in this Map, and access the value stored in Thread through a specific object. Such an object is ThreadLocal.

Through the get method of ThreadLocal, you can return values bound to different Thread objects.

ThreadLocalMap

Above we simply treat ThreadLocalMap as a map. In fact, ThreadLocalMap is an object, and each value stored in it is an Entry.

This Entry is different from the Entry in Map, it is a static inner class:

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

Note that the Entry here inherits from WeakReference, which means that the key of this Entry is a weak reference object. If the key does not have a strong reference, it will be recycled in gc. Thereby ensuring the validity of the data in the Map.

The values in ThreadLocalMap are stored in the Entry array:

 private Entry[] table;

Let's see how to get a value from ThreadLocalMap:

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

key.threadLocalHashCode can be simply regarded as the value of the key represented by ThreadLocal.

And key.threadLocalHashCode & (table.length - 1) is used to calculate the index of the current key in the table.

The bit operation is used here to improve the calculation speed. Actually this calculation is equivalent to:

 key.threadLocalHashCode % table.length

is a modulo operation.

If you search according to the index of the modulo operation, if you find it, return it directly.

If it is not found, it will traverse and call the nextIndex method and modify the value of index until the search is complete:

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

Recycler

ThreadLocal essentially binds the ThreadLocal object to different Threads, and the values stored in different Threads can be obtained through the get method of ThreadLocal.

At the end of the day, ThreadLocal and Thread have a one-to-many relationship. Because ThreadLocal is a weak reference in ThreadLocalMap, when ThreadLocal is set to empty, the objects in the corresponding ThreadLocalMap will be recycled in the next garbage collection process, thus saving a space for ThreadLocalMap in Thread.

Then when our Thread is a long-running Thread, if a lot of objects with a short life cycle are allocated in this Thread, a lot of garbage objects to be collected will be generated, which will put pressure on the garbage collector.

To solve this problem, netty provides us with the Recycler class to recycle these short-lived objects. Next, let's explore how Recycler works.

Before that, let's take a look at how to use Recycler.

 public class MyObject {

  private static final Recycler<MyObject> RECYCLER = new Recycler<MyObject>() {
    protected MyObject newObject(Recycler.Handle<MyObject> handle) {
      return new MyObject(handle);
    }
  }

  public static MyObject newInstance(int a, String b) {
    MyObject obj = RECYCLER.get();
    obj.myFieldA = a;
    obj.myFieldB = b;
    return obj;
  }
    
  private final Recycler.Handle<MyObject> handle;
  private int myFieldA;
  private String myFieldB;

  private MyObject(Handle<MyObject> handle) {
    this.handle = handle;
  }
  
  public boolean recycle() {
    myFieldA = 0;
    myFieldB = null;
    return handle.recycle(this);
  }
}

MyObject obj = MyObject.newInstance(42, "foo");
...
obj.recycle();

Essentially, Recycler is like a factory class that generates corresponding class objects through its get method. When the object needs to be recycled, call the recycle method in Recycler.Handle to recycle the object.

First look at the get method of the generated object:

 public final T get() {
        if (maxCapacityPerThread == 0) {
            return newObject((Handle<T>) NOOP_HANDLE);
        }
        Stack<T> stack = threadLocal.get();
        DefaultHandle<T> handle = stack.pop();
        if (handle == null) {
            handle = stack.newHandle();
            handle.value = newObject(handle);
        }
        return (T) handle.value;
    }

The meaning of the above code is to first judge whether the maximum capacity allowed by a single thread is exceeded, and if so, return a new object and bind an empty handler, indicating that the newly created object cannot be recycled.

If not, get the Stack bound to the current thread from threadLocal. Then take the top element from the Stack, if there is no object in the Stack, create a new object and bind the handle.

Finally, return the object bound to the handle.

Take a look at the recycle object method recycle of handle:

 public void recycle(Object object) {
            if (object != value) {
                throw new IllegalArgumentException("object does not belong to handle");
            }

            Stack<?> stack = this.stack;
            if (lastRecycledId != recycleId || stack == null) {
                throw new IllegalStateException("recycled already");
            }

            stack.push(this);
        }

The above code first determines whether the object bound to the handle is an object to be recycled. Recycle only when it is equal.

The essence of recycling is to push the object to the stack for subsequent retrieval.

Therefore, the reason why Recycler can save the number of garbage collected objects is that it will store the objects that are no longer used in the Stack, and return them for reuse in the next get. This is why we need to reset object properties when recycling:

 public boolean recycle() {
    myFieldA = 0;
    myFieldB = null;
    return handle.recycle(this);
  }

Summarize

If you have multiple short-lived objects of the same type in a thread, try Recycle.

This article has been included in http://www.flydean.com/47-netty-thread-…al-object-pool-2/

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