关于java:可能有人听过ThreadLocal但一定没人听过ThreadLocal对象池

3次阅读

共计 5074 个字符,预计需要花费 13 分钟才能阅读完成。

简介

JDK 中的 Thread 大家必定用过,只有是用过异步编程的同学必定都相熟。为了保留 Thread 中特有的变量,JDK 引入了 ThreadLocal 类来专门对 Thread 的本地变量进行治理。

ThreadLocal

很多新人可能不明确 ThreadLocal 到底是什么,它和 Thread 到底有什么关系。

其实很简略,ThreadLocal 实质上是一个 key,它的 value 就是 Thread 中一个 map 中存储的值。

每个 Thread 中都有一个 Map, 这个 Map 的类型是 ThreadLocal.ThreadLocalMap。咱们先不具体探讨这个 ThreadLocalMap 到底是怎么实现的。当初就简略将其看做是一个 map 即可。

接下来,咱们看下一个 ThreadLocal 的工作流程。

首先来看一下 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();
       }
   }

下面的类是做什么用的呢?

当你在不同的线程环境中调用 ThreadId 的 get 办法时候,会返回不同的 int 值。所以能够看做是 ThreadId 为每个线程生成了一个线程 ID。

咱们来看下它是怎么工作的。

首先咱们调用了 ThreadLocal<Integer> 的 get 办法。ThreadLocal 中的 get 办法定义如下:

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

get 办法中,咱们第一步获取以后的线程 Thread,而后 getMap 返回以后 Thread 中的 ThreadLocalMap 对象。

如果 Map 不为空,则取出以以后 ThreadLocal 为 key 对应的值。

如果 Map 为空,则调用初始化办法:

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

初始化办法首先判断以后 Thread 中的 ThreadLocalMap 是否为空,如果不为空则设置初始化的值。

如果为空则创立新的 Map:

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

大家能够看到整个 ThreadLocalMap 中的 Key 就是 ThreadLocal 自身,而 Value 就是 ThreadLocal 中定义的泛型的值。

当初咱们来总结一下 ThreadLocal 到底是做什么的。

每个 Thread 中都有一个 ThreadLocal.ThreadLocalMap 的 Map 对象,咱们心愿向这个 Map 中寄存一些特定的值,通过一个特定的对象来拜访到寄存在 Thread 中的这个值,这样的对象就是 ThreadLocal。

通过 ThreadLocal 的 get 办法,就能够返回绑定到不同 Thread 对象中的值。

ThreadLocalMap

下面咱们简略的将 ThreadLocalMap 看做是一个 map。事实上 ThreadLocalMap 是一个对象,它外面寄存的每个值都是一个 Entry.

这个 Entry 不同于 Map 中的 Entry,它是一个 static 的外部类:

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

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

留神,这里的 Entry 继承自 WeakReference,示意这个 Entry 的 key 是弱援用对象,如果 key 没有强援用的状况下,会在 gc 中被回收。从而保障了 Map 中数据的有效性。

ThreadLocalMap 中的值都寄存在 Entry 数组中:

private Entry[] table;

咱们看一下怎么从 ThreadLocalMap 中 get 一个值的:

        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 能够简略的看做是 ThreadLocal 代表的 key 的值。

而 key.threadLocalHashCode & (table.length – 1) 则应用来计算以后 key 在 table 中的 index。

这里应用的是位运算,用来晋升计算速度。实际上这个计算等同于:

key.threadLocalHashCode % table.length

是一个取模运算。

如果依照取模运算的 index 去查找,找到就间接返回。

如果没找到则会遍历调用 nextIndex 办法,批改 index 的值,只到查找结束为止:

        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 实质上是将 ThreadLocal 这个对象和不同的 Thread 进行绑定,通过 ThreadLocal 的 get 办法能够取得存储在不同 Thread 中的值。

归根结底,ThreadLocal 和 Thread 是一对多的关系。因为 ThreadLocal 在 ThreadLocalMap 中是弱援用,所以当 ThreadLocal 被置为空之后,对应的 ThreadLocalMap 中的对象会在下一个垃圾回收过程中被回收, 从而为 Thread 中的 ThreadLocalMap 节俭一个空间。

那么当咱们的 Thread 是一个长时间运行的 Thread 的时候,如果在这个 Thread 中调配了很多生命周期很短的对象,那么会生成很多待回收的垃圾对象,给垃圾回收器造成压力。

为了解决这个问题,netty 为咱们提供了 Recycler 类,用来回收这些短生命周期的对象。接下来,咱们来探索一下 Recycler 到底是怎么工作的。

在这之前,咱们先看下怎么应用 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();

实质上,Recycler 就像是一个工厂类,通过它的 get 办法来生成对应的类对象。当这个对象须要被回收的时候,调用 Recycler.Handle 中的 recycle 办法,即可将对象回收。

先看一下生成对象的 get 办法:

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

下面代码的含意就是,先判断是否超过了单个线程容许的最大容量,如果是,则返回一个新的对象,绑定一个空的 handler,示意这个新创建的对象是不能够被回收的。

如果不是,则从 threadLocal 中拿到以后线程绑定的 Stack。而后从 Stack 中取出最下面的元素,如果 Stack 中没有对象,则创立新的对象,并绑定 handle。

最初返回 handle 绑定的对象。

再看一下 handle 的回收对象办法 recycle:

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

下面的代码先去判断和 handle 绑定的对象是不是要回收的对象。只有相等的时候才进行回收。

而回收的实质就是将对象 push 到 stack 中,供后续 get 的时候取出应用。

所以,Recycler 可能节约垃圾回收对象个数的起因是,它会将不再应用的对象存储到 Stack 中,并在下次 get 的时候返回,重复使用。这也就是咱们在回收须要重置对象属性的起因:

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

总结

如果你在一个线程中会有多个同类型的短生命周期对象,那么无妨试试 Recycle 吧。

本文已收录于 http://www.flydean.com/47-netty-thread-…al-object-pool-2/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0