💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
netty在内存分配中大量使用了线程本地缓存,并对ThreadLocal进行了扩展。 ## 9.3.1 java的ThreadLocal **使用** ThreadLocal为每个线程存储了其对应的副本,下面的例子中,我们启动多个线程,每个线程保存不同的数据。 ``` public class JavaThreadLocalDemo2 { public static void main(String[] args) { Task task = new Task(); Thread tasks[] = new Thread[10]; for(int i=0;i<10;i++) { tasks[i] = new Thread(task); } for(int i=0;i<10;i++) { tasks[i].start(); } } static class Task implements Runnable{ ThreadLocal<Float> local = new ThreadLocal<Float>(); @Override public void run() { Float process = new Random().nextFloat(); local.set(process); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) {} System.out.println(Thread.currentThread().getName() +" "+local.get() +" "+ (local.get()==process)); } } } ``` **实现** 每个Thread实例的内部有一个ThreadLocal.ThreadLocalMap对象的实例threadLocals,内部有一个Entry[] table用来保存threadLocal和其对应的Object;每个ThreadLocal有一个唯一的threadLocalHashCode,每次调用set时,先获取当前线程的ThreadLocalMap,然后使用map的set方法将ThreadLocal和value设置进去。 ``` public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ``` map的set中,首先根据ThreadLocal的哈希值获取其在table中的位置,如果该位置内容为null,保存到这个元素上即可;如果该位置元素不为null且key为相同ThreadLocal,替换value;如果该位置元素不为null且key为null(出现null的原因是由于Entry的key是继承了软引用,在下一次GC时不管它有没有被引用都会被回收掉,Value不会被回收)。当出现null时,会调用replaceStaleEntry()方法接着循环寻找相同的key,如果存在,直接替换旧值。如果不存在,则在当前位置上重新创建新的Entry。如果该位置元素不为相同ThreadLocal且不为null,说明其他ThreadLocal已经使用,遍历链表查找其他位置。 保存到table之后,cleanSomeSlots会查询是否有过期的元素,如果有并且大于阀值(超过2/3),执行rehash() ``` private void set(ThreadLocal<?> key, Object value) { 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(); } ``` map的get方法中,获取当前线程的ThreadLocalMap后,在getEntry中获取ThreadLocal对于的值。getEntry方法会根据index查找,由于可能发生冲突,会调用getEntryAfterMiss遍历数组。 ``` 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(); } 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); } ``` 通过对java的ThreadLocal实现的了解,我们可以看到,在set时会发生冲突,会遍历寻找合适的位置,而在get时如果首次未命中,也会遍历寻找ThreadLocal对应的值。在Netty高并发中,会有频繁的读取,因此Netty自己实现了ThreadLocal以提高效率。 # Netty的ThreadLocal 首先,Netty将线程分为了两种,一种是Netty实现的可快速存取本地缓存的FastThreadLocalThread,一种是普通的Thread。这点在线程池创建过程中使用的`DefaultThreadFactory`中可以看到,线程池创建的线程都是FastThreadLocalThread。FastThreadLocalThread中有一个变量InternalThreadLocalMap保存本地的相关数据。 在线程中,netty讲线程的本地变量都保存在InternalThreadLocalMap中,这个对象看名字是个Map,但其实里面由数组indexedVariables保存设置的本地变量。数组indexedVariables的大小默认为32,每个线程内部都有一个InternalThreadLocalMap的实例,可以设置多个ThreadLocal,这个ThreadLocal不是Java默认的ThreadLocal,而是使用FastThreadLocal,每个FastThreadLocal有一个唯一的Id保存在index中,对应着FastThreadLocalThread内部的InternalThreadLocalMap的位置。因此,netty相比Java的ThreadLocal来说,每个key都有固定的index,这样不会发生冲突,从而提高了效率。 netty中如果使用的是普通线程的ThreadLocal,那么会在Thread的ThreadLocalMap中保存InternalThreadLocalMap,FastThreadLocal存放在其index对应的位置上。 ![NettyThreadLocal](http://web.uxiaowo.com/netty/Future/ThreadLocal.png) # 测试 CompareJavaTL使用java的ThreadLocal,写/读时间为338ms和248ms CompareNettyTL使用Netty的FastThreadLocal,写/读时间为283ms和8ms 可见,Netty的ThreadLocal机制在读写时效率都要比java的高,根据之前的分析, ``` public class CompareJavaTL { static int count = 100000000; static int times = 100000000; public static void main(String[] args) { ThreadLocal<String> tl = new ThreadLocal<String>(); long begin = System.currentTimeMillis(); for(int i=0;i<count;i++) { tl.set("javatl"); } System.out.println(System.currentTimeMillis()-begin); begin = System.currentTimeMillis(); for(int i=0;i<times;i++) { tl.get(); } System.out.println(System.currentTimeMillis()-begin); } } ``` ``` public class CompareNettyTL { static int count = 100000000; static int times = 100000000; public static void main(String[] args) { new FastThreadLocalThread(new Runnable() { @Override public void run() { FastThreadLocal<String> tl = new FastThreadLocal<String>(); long begin = System.currentTimeMillis(); for(int i=0;i<count;i++) { tl.set("javatl"); } System.out.println(System.currentTimeMillis()-begin); begin = System.currentTimeMillis(); for(int i=0;i<times;i++) { tl.get(); } System.out.println(System.currentTimeMillis()-begin); } }).start(); } } ```