前言:

ThreadLocal在JDK中是一个非常重要的工具类,通过阅读源码,可以在各大框架都能发现它的踪影。它最经典的应用就是 事务管理 ,同时它也是面试中的常客。

今天就来聊聊这个ThreadLocal;本文主线:

①、ThreadLocal 介绍

②、ThreadLocal 实现原理

③、ThreadLocal 内存泄漏分析

④、ThreadLocal 应用场景及示例

注:本文源码基于 JDK1.8

ThreadLocal 介绍:

正如 JDK 注释中所说的那样: ThreadLocal 类提供线程局部变量,它通常是私有类中希望将状态与线程关联的静态字段。

简而言之,就是 ThreadLocal 提供了线程间数据隔离的功能,从它的命名上也能知道这是属于一个线程的本地变量。也就是说,每个线程都会在 ThreadLocal 中保存一份该线程独有的数据,所以它是线程安全的。

熟悉 Spring 的同学可能知道 Bean 的作用域(Scope),而 ThreadLocal 的作用域就是线程。

下面通过一个简单示例来展示一下 ThreadLocal 的特性:

public static void main(String[] args) {ThreadLocal<String> threadLocal = new ThreadLocal<>();// 创建一个有2个核心线程数的线程池ExecutorService threadPool = new ThreadPoolExecutor(2, 2, 1, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10));// 线程池提交一个任务,将任务序号及执行该任务的子线程的线程名放到 ThreadLocal 中threadPool.execute(() -> threadLocal.set("任务1: " + Thread.currentThread().getName()));threadPool.execute(() -> threadLocal.set("任务2: " + Thread.currentThread().getName()));threadPool.execute(() -> threadLocal.set("任务3: " + Thread.currentThread().getName()));// 输出 ThreadLocal 中的内容for (int i = 0; i < 10; i++) {threadPool.execute(() -> System.out.println("ThreadLocal value of " + Thread.currentThread().getName() + " = " + threadLocal.get()));}// 线程池记得关闭threadPool.shutdown();
}

上面代码首先创建了一个有2个核心线程数的普通线程池,随后提交一个任务,将任务序号及执行该任务的子线程的线程名放到 ThreadLocal 中,最后在一个 for 循环中输出线程池中各个线程存储在 ThreadLocal 中的值。

这个程序的输出结果是:

ThreadLocal value of pool-1-thread-1 = 任务3: pool-1-thread-1
ThreadLocal value of pool-1-thread-2 = 任务2: pool-1-thread-2
ThreadLocal value of pool-1-thread-1 = 任务3: pool-1-thread-1
ThreadLocal value of pool-1-thread-2 = 任务2: pool-1-thread-2
ThreadLocal value of pool-1-thread-1 = 任务3: pool-1-thread-1
ThreadLocal value of pool-1-thread-2 = 任务2: pool-1-thread-2
ThreadLocal value of pool-1-thread-1 = 任务3: pool-1-thread-1
ThreadLocal value of pool-1-thread-2 = 任务2: pool-1-thread-2
ThreadLocal value of pool-1-thread-1 = 任务3: pool-1-thread-1
ThreadLocal value of pool-1-thread-2 = 任务2: pool-1-thread-2

由此可见,线程池中执行提交的任务的是名为 pool-1-thread-1 的线程,随后多次输出线程池核心线程在 ThreadLocal 变量中存储的的内容也表明:每个线程在 ThreadLocal 中存储的内容是当前线程独有的,在多线程环境下,能够有效防止自己的变量被其他线程修改(存储的内容是同一个引用类型对象的情况除外)。

ThreadLocal 实现原理:

在 JDK1.8 版本中 ThreadLocal 类的源码总共723行,去掉注释大概有350行,应该算是 JDK 核心类库中代码量比较少的一个类了,相对来说它的源码还是挺容易理解的。

下面,就从 ThreadLocal 的数据结构开始聊聊它的实现原理吧。

底层数据结构:

ThreadLocal 底层是通过 ThreadLocalMap 这个静态内部类来存储数据的,ThreadLocalMap 就是一个键值对的 Map,它的底层是 Entry 对象数组,Entry 对象中存放的键是 ThreadLocal 对象,值是 Object 类型的具体存储内容。

除此之外,ThreadLocalMap 也是 Thread 类一个属性。

如何证明上面给出的 ThreadLocal 类底层数据结构的正确性?

我们可以从 ThreadLocal#get() 方法开始追踪代码,看看线程局部变量到底是从哪里被取出来的。

public T get() {// 获取当前线程Thread t = Thread.currentThread();// 获取 Thread 类中 ThreadLocal.ThreadLocalMap 类型的 threadLocals 变量ThreadLocalMap map = getMap(t);// 若 threadLocals 变量不为空,根据 ThreadLocal 对象来获取 key 对应的 valueif (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 若 threadLocals 变量是 NULL,初始化一个新的 ThreadLocalMap 对象return setInitialValue();
}// ThreadLocal#setInitialValue
// 初始化一个新的 ThreadLocalMap 对象
private T setInitialValue() {// 初始化一个 NULL 值T value = initialValue();// 获取当前线程Thread t = Thread.currentThread();// 获取 Thread 类中 ThreadLocal.ThreadLocalMap 类型的 threadLocals 变量ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}// ThreadLocalMap#createMap
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

通过 ThreadLocal#get() 方法可以很清晰的看到,我们根据 ThreadLocal 对象从 ThreadLocal 中读取数据时,首先会获取当前线程对象,然后得到当前线程对象中 ThreadLocal.ThreadLocalMap 类型的 threadLocals 属性;

如果 threadLocals 属性不为空,会根据 ThreadLocal 对象作为 key 来获取 key 对应的 value;如果 threadLocals 变量是 NULL,就初始化一个新的ThreadLocalMap 对象。

再看 ThreadLocalMap 的构造方法,也就是 Thread 类中 ThreadLocal.ThreadLocalMap 类型的 threadLocals 属性不为空时的执行逻辑。

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

这个构造方法其实是将 ThreadLocal 对象作为 key,存储的具体内容 Object 对象作为 value,包装成一个 Entry 对象,放到 ThreadLocalMap 类中类型为 Entry 数组的 table 属性中,这样就完成了线程局部变量的存储。

所以说, ThreadLocal 中的数据最终是存放在 ThreadLocalMap 这个类中的

散列方式:

ThreadLocalMap#set(ThreadLocal<?> key, Object value) 方法中我写了一行注释:

// 获取当前 ThreadLocal 对象的散列值
int i = key.threadLocalHashCode & (len-1);

这行代码得到的值其实是一个 ThreadLocal 对象的散列值,这就是 ThreadLocal 的散列方式,我们称之为 斐波那契散列

// ThreadLocal#threadLocalHashCode
private final int threadLocalHashCode = nextHashCode();// ThreadLocal#nextHashCode
private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
}// ThreadLocal#nextHashCode
private static AtomicInteger nextHashCode = new AtomicInteger();// AtomicInteger#getAndAdd
public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);
}// 魔数 ThreadLocal#HASH_INCREMENT
private static final int HASH_INCREMENT = 0x61c88647;

key.threadLocalHashCode 所涉及的函数及属性如上所示,每一个 ThreadLocal 的 threadLocalHashCode 属性都是基于魔数 0x61c88647 来生成的。

这里就不讨论选择这个魔数的原因了(其实是我看不太懂),总之大量的实践证明: 使用 0x61c88647 作为魔数生成的 threadLocalHashCode 再与2的幂取余,得到的结果分布很均匀。

注: 对 A 进行2的幂取余操作 A % 2^N 可以通过 A & (2^n-1) 来代替,位运算的效率比取模效率高很多。

如何解决哈希冲突:

我们已经知道 ThreadLocalMap 类的底层数据结构是一个 Entry 类型的数组,但与 HashMap 中的 Node 类数组+链表形式不同的是,Entry 类没有 next 属性来构成链表,所以它是一个单纯的数组。

就算上面所说的 斐波那契散列法 真的能够充分散列,但难免还是可能会发生哈希碰撞,那么问题来了,Entry 数组是如何解决哈希冲突的?

这就需要拿出 ThreadLocal#set(T value) 方法了,而具体处理哈希冲突的逻辑是在 ThreadLocalMap#set(ThreadLocal<?> key, Object value) 方法中的:

public void set(T value) {// 获取当前线程Thread t = Thread.currentThread();// 获取 Thread 类中 ThreadLocal.ThreadLocalMap 类型的 threadLocals 变量ThreadLocalMap map = getMap(t);// 若 threadLocals 变量不为空,进行赋值;否则新建一个 ThreadLocalMap 对象来存储if (map != null)map.set(this, value);elsecreateMap(t, value);
}// ThreadLocalMap#set
private void set(ThreadLocal<?> key, Object value) {// 获取 ThreadLocalMap 的 Entry 数组对象Entry[] tab = table;int len = tab.length;// 基于斐波那契散列法获取当前 ThreadLocal 对象的散列值int i = key.threadLocalHashCode & (len-1);// 解决哈希冲突,线性探测法for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();// 代码(1)if (k == key) {e.value = value;return;}// 代码(2)if (k == null) {replaceStaleEntry(key, value, i);return;}}// 代码(3)将 key-value 包装成 Entry 对象放在数组退出循环时的位置中tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}// ThreadLocalMap#nextIndex
// Entry 数组的下一个索引,若超过数组大小则从0开始,相当于环形数组
private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);
}

具体分析处理哈希冲突的 ThreadLocalMap#set(ThreadLocal<?> key, Object value) 方法,可以看到,在拿到 ThreadLocal 对象的散列值之后进入了一个 for 循环,循环的条件也很清楚:从 Entry 数组的 ThreadLocal 对象散列值处开始,每次向后挪一位,如果超过数组大小则从0开始继续遍历,直到 Entry 对象为 NULL 为止。

在循环过程中:

  • 如代码(1),如果当前 ThreadLocal 对象正好等于 Entry 对象中的 key 属性,直接更新 ThreadLocal 中 value 的值;
  • 如代码(2),如果当前 ThreadLocal 对象不等于 Entry 对象中的 key 属性,并且 Entry 对象的 key 是空的,这里进行的逻辑其实是 设置键值对,同时清理无效的 Entry (一定程序防止内存泄漏,下文会有详细介绍);
  • 如代码(3),如果在遍历中没有发现当前 TheadLocal 对象的散列值,也没有发现 Entry 对象的 key 为空的情况,而是满足了退出循环的条件,即 Entry 对象为空时,那么就会创建一个 新的 Entry 对象进行存储 ,同时做一次 启发式清理 ,将 Entry 数组中 key 为空,value 不为空的对象的 value 值释放;

至此,我们分析完了在向 ThreadLocal 中存储数据时,拿到 ThreadLocal 对象散列值之后的逻辑,回到本小节的主题—— ThreadLocal 是如何解决哈希冲突的?

由上面的代码可以知道,在基于斐波那契散列法获取当前 ThreadLocal 对象的散列值之后进入了一个循环,在循环中是处理具体处理哈希冲突的方法:

  • 如果散列值已存在且 key 为同一个对象,直接更新 value
  • 如果散列值已存在但 key 不是同一个对象,尝试在下一个空的位置进行存储

所以,来总结一下 ThreadLocal 处理哈希冲突的方式就是:如果在 set 时遇到哈希冲突,ThreadLocal 会通过线性探测法尝试在数组下一个索引位置进行存储,同时在 set 过程中 ThreadLocal 会释放 key 为 NULL,value 不为 NULL 的脏 Entry对象的 value 属性来防止内存泄漏

初始容量及扩容机制:

在上文中有提到过 ThreadLocalMap 的构造方法,这里详细说明一下。

// ThreadLocalMap 构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {// 初始化 Entry 数组table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;// 设置扩容条件setThreshold(INITIAL_CAPACITY);
}

ThreadLocalMap 的初始容量是 16:

// 初始化容量
private static final int INITIAL_CAPACITY = 16;

下面聊一下 ThreadLocalMap 的扩容机制 ,它在扩容前有两个判断的步骤,都满足后才会进行最终扩容:

  • ThreadLocalMap#set(ThreadLocal<?> key, Object value) 方法中可能会触发启发式清理,在清理无效 Entry 对象后,如果数组长度大于等于数组定义长度的 2/3,则首先进行 rehash;
// rehash 条件
private void setThreshold(int len) {threshold = len * 2 / 3;
}
  • rehash 会触发一次全量清理,如果数组长度大于等于数组定义长度的 1/2,则进行 resize(扩容);
// 扩容条件
private void rehash() {expungeStaleEntries();// Use lower threshold for doubling to avoid hysteresisif (size >= threshold - threshold / 4)resize();
}
  • 进行扩容时,Entry 数组为扩容为 原来的2倍 ,重新计算 key 的散列值,如果遇到 key 为 NULL 的情况,会将其 value 也置为 NULL,帮助虚拟机进行GC。
// 具体的扩容函数
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;
}

父子线程间局部变量如何传递:

我们已经知道 ThreadLocal 中存储的是线程的局部变量,那如果现在有个需求,想要实现线程间局部变量传递,这该如何实现呢?

大佬们早已料到会有这样的需求,于是设计出了 InheritableThreadLocal 类。

InheritableThreadLocal 类的源码除去注释之外一共不超过10行,因为它是继承于 ThreadLocal 类,很多东西在 ThreadLocal 类中已经实现了,InheritableThreadLocal 类只重写了其中三个方法:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {protected T childValue(T parentValue) {return parentValue;}ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}

我们先用一个简单的示例来实践一下父子线程间局部变量的传递功能。

public static void main(String[] args) {ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();threadLocal.set("这是父线程设置的值");new Thread(() -> System.out.println("子线程输出:" + threadLocal.get())).start();
}// 输出内容
子线程输出:这是父线程设置的值

可以看到,在子线程中通过调用 InheritableThreadLocal#get() 方法,拿到了在父线程中设置的值。

那么,这是如何实现的呢?

实现父子线程间的局部变量共享需要追溯到 Thread 对象的构造方法:

public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}private void init(ThreadGroup g, Runnable target, String name, long stackSize) {init(g, target, name, stackSize, null, true);
}private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,// 该参数一般默认是 trueboolean inheritThreadLocals) {// 省略大部分代码Thread parent = currentThread();// 复制父线程的 inheritableThreadLocals 属性,实现父子线程局部变量共享if (inheritThreadLocals && parent.inheritableThreadLocals != null) {this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }// 省略部分代码
}

在最终执行的构造方法中,有这样一个判断:如果当前父线程(创建子线程的线程)的 inheritableThreadLocals 属性不为 NULL,就会将当下父线程的 inheritableThreadLocals 属性复制给子线程的 inheritableThreadLocals 属性。具体的复制方法如下:

// ThreadLocal#createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];// 一个个复制父线程 ThreadLocalMap 中的数据for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {// childValue 方法调用的是 InheritableThreadLocal#childValue(T parentValue)Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}
}

需要注意的是,复制父线程共享变量的时机是在创建子线程时,如果在创建子线程后父线程再往 InheritableThreadLocal 类型的对象中设置内容,将不再对子线程可见。

ThreadLocal 内存泄漏分析:

最后再来说说 ThreadLocal 的内存泄漏问题,众所周知,如果使用不当,ThreadLocal 会导致内存泄漏。

内存泄漏 是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

发生内存泄漏的原因:

而 ThreadLocal 发生内存泄漏的原因需要从 Entry 对象说起。

// ThreadLocal->ThreadLocalMap->Entry
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

Entry 对象的 key 即 ThreadLocal 类是继承于 WeakReference 弱引用类。具有弱引用的对象有更短暂的生命周期,在发生 GC 活动时,无论内存空间是否足够,垃圾回收器都会回收具有弱引用的对象。

由于 Entry 对象的 key 是继承于 WeakReference 弱引用类的,若 ThreadLocal 类没有外部强引用,当发生 GC 活动时就会将 ThreadLocal 对象回收。

而此时如果创建 ThreadLocal 类的线程依然活动,那么 Entry 对象中 ThreadLocal 对象对应的 value 就依旧具有强引用而不会被回收,从而导致内存泄漏。

如何解决内存泄漏问题:

要想解决内存泄漏问题其实很简单,只需要记得在使用完 ThreadLocal 中存储的内容后将它 remove 掉就可以了。

这是主动防止发生内存泄漏问题的手段,但其实设计 ThreadLocal 的大神当然也发现了 ThreadLocal 可能引发内存泄漏的问题,所以他们也设计了相应的手段来防止内存泄漏。

ThreadLocal 内部如何防止内存泄漏:

在上文中描述 ThreadLocalMap#set(ThreadLocal<?> key, Object value) 其实已经有涉及 ThreadLocal 内部清理无效 Entry 的逻辑了,在通过线性检测法处理哈希冲突时,若 Entry 数组的 key 与当前 ThreadLocal 不是同一个对象,同时 key 为空的时候,会进行 清理无效 Entry 的处理,即 ThreadLOcalMap#replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) 方法:

  • 这个方法中也是一个循环,循环的逻辑与 ThreadLocalMap#set(ThreadLocal<?> key, Object value) 方法一致;
  • 在循环过程中如果找到了将要存储的 ThreadLocal 对象,则会将它与进入 replaceStaleEntry 方法时满足条件的 k 值做交换,同时将 value 更新;
  • 如果没有找到将要存储的 ThreadLocal 对象,则会在此 k 值处新建一个 Entry 对象存储;
  • 同时,在循环过程中如果发现其他无效的 Entry( key 为 NULL,value还在的情况,可能导致内存泄漏,下文会有详细描述),会顺势找到 Entry 数组中所有的无效 Entry,释放这些无效 Entry(通过将 key 和 value 都设置为NULL),在一定程度上避免了内存泄漏;

如果满足线性检测循环结束条件了,即遇到了 Entry==NULL 的情况,就新建一个 Entry 对象来存储数据。然后会进行一次启发式清理,如果启发式清理没有成功释放满足条件的对象,同时满足扩容条件时,会执行 ThreadLocalMap#rehash() 方法。

private void rehash() {// 全量清理expungeStaleEntries();// 满足条件则扩容if (size >= threshold - threshold / 4)resize();
}

ThreadLocalMap#rehash() 方法中会对 ThreadLocalMap 进行一次全量清理,全量清理会遍历整个 Entry 数组,删除所有 key 为 NULL,value 不为 NULL 的脏 Entry对象。

// 全量清理
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);}
}

进行全量清理之后,如果 Entry 数组的大小大于等于 threshold - threshold / 4 ,则会进行2倍扩容。

总结一下:在ThreadLocal 内部是通过在 get、set、remove 方法中主动进行清理 key 为 NULL 且 value 不为 NULL 的无效 Entry 来避免内存泄漏问题。

但是基于 get、set 方法让 ThreadLocal 自行清理无效 Entry 对象并不能完全避免内存泄漏问题,要彻底解决内存泄漏问题还得养成使用完就主动调用 remove 方法释放资源的好习惯。

ThreadLocal 应用场景及示例:

ThreadLocal 在很多开源框架中都有应用,比如:Spring 中的事务隔离级别的实现、MyBatis 分页插件 PageHelper 的实现。

同时,我在项目中也有基于 ThreadLocal 与过滤器实现接口白名单的鉴权功能。

小结:

以面试题的形式来总结一下关于 ThreadLocal 本文所描述的内容:

  • ThreadLocal 解决了哪些问题
  • ThreadLocal 底层数据结构
  • ThreadLocalMap 的散列方式
  • ThreadLocalMap 如何处理哈希冲突
  • ThreadLocalMap 扩容机制
  • ThreadLocal 如何实现父子线程间局部变量共享
  • ThreadLocal 为什么会发生内存泄漏
  • ThreadLocal 内存泄漏如何解决
  • ThreadLocal 内部如何防止内存泄漏,在哪些方法中存在
  • ThreadLocal 应用场景

❤ 点赞 + 评论 + 转发 哟

您可以VX搜索【木子雷】公众号,坚持高质量原创java技术文章,福利多多哟!

如果本文对大家有帮助的话,请多多点赞评论呀,你们的支持就是我不断创作的动力!

一文让你彻底明白ThreadLocal相关推荐

  1. code vs 集成tfs_关于编译器和集成开发环境,一文给你讲明白!

    公众号:C语言编程 整理:薛定谔的coding猫 各位,关于编译器和集成开发环境这两个名称,我们平时一直在说,但这二位究竟有什么区别和联系呢,今天就跟大家简单聊一聊. 预备知识 我们平时所说的程序,是 ...

  2. 缓存穿透、击穿、雪崩什么的傻傻分不清楚?看了这篇文后,我明白了

    对于缓存,大家肯定都不陌生,不管是前端还是服务端开发,缓存几乎都是必不可少的优化方式之一.在实际生产环境中,缓存的使用规范也是一直备受重视的,如果使用的不好,很容易就遇到缓存击穿.雪崩等严重异常情景, ...

  3. 一文带你学明白java虚拟机:C1编译器,HIR代码优化

    HIR代码优化 为了减少编译时间,C1在抽象解释生成HIR期间,每生成一条SSA指令,都会调用append_with_bci努力尝试若干局部优化.除此之外,HIR构造完成之后,C1还会执行若干轻量级全 ...

  4. 一文带你怼明白进程和线程通信原理

    进程间通信 进程是需要频繁的和其他进程进行交流的.例如,在一个 shell 管道中,第一个进程的输出必须传递给第二个进程,这样沿着管道进行下去.因此,进程之间如果需要通信的话,必须要使用一种良好的数据 ...

  5. 什么是ROC和AUC?一文给你讲明白

    原文链接:https://www.cnblogs.com/gatherstars/p/6084696.html ROC曲线与AUC值 本文根据以下文章整理而成,链接: (1)http://blog.c ...

  6. AXI 一文就能讲明白

    前言 1.简介 AXI是个什么东西呢,它其实不属于Zynq,不属于Xilinx,而是属于ARM.它是ARM最新的总线接口,以前叫做AMBA,从3.0以后就称为AXI了. AXI(Advanced eX ...

  7. 一文让你彻底明白,到底什么是用户画像?

    写在前面: 博主是一名大数据的初学者,昵称来源于<爱丽丝梦游仙境>中的Alice和自己的昵称.作为一名互联网小白,写博客一方面是为了记录自己的学习历程,一方面是希望能够帮助到很多和自己一样 ...

  8. 前端交接文档_开发型Web前端和设计型Web前端的区别是什么?

    小编说学Web前端,你弄懂开发型Web前端和设计型Web前端的区别了吗?今天千锋广州小编给大家梳理一下设计型Web前端做什么?都要学习什么? 想必大家也会遇到这种情况,要做一个项目,产品经理说产品原型 ...

  9. android Word 显示文档结构图

    今天,简单讲讲android里浏览Word文档时,如何显示文档的文档结构图. 这个其实也很简单,之前我把自己用WPS写成的文档发送给领导查看,他用的是Word查看的文档,所以没有显示我在WPS上设置的 ...

最新文章

  1. JavaScript--练习1--99乘法表
  2. 2021年春节联欢晚会第三次联排亮点多
  3. 虚拟机从暂停状态恢复后HEALTH_WARN,osds down
  4. Virtio: An I/O virtualization framework for Linux
  5. java tomcat输出信息,java – 如何在Tomcat中记录stdout输出?
  6. multism中ui和uo应该怎么表示_第310 这四个常考英语单词,到底表示时间还是地点?...
  7. (转)2017中国互联网证券年度报告
  8. 软件测试用例最简单最常见的模板和案例(QQ登陆,手机号,126邮箱)
  9. java链表实现多项式的运算
  10. POI库读取xlsx和xls格式excel以及解决安卓上的适配
  11. Chapter 5 Eigenvalues and Eigenvectors
  12. 裸金属服务器是什么?有什么特点?
  13. 弧度的定义? 180度(角度)=3.14(3.14弧度)
  14. [USACO07FEB] Lilypad Pond
  15. MFC/C++调用易语言的整数型+文本型与VS2010互动
  16. Java Tank类
  17. 546计算机综合什么意思,装系统出现546怎么设置
  18. Matlab使用笔记(三):matlab设置代码自动补全功能
  19. 26岁,2020 - 观周浩《书记》
  20. 绝地求生大逃杀DLL140dll出错怎么办?

热门文章

  1. 开源BI报表及OLAP多维分析平台OPENI(二)—搭建Eclipse下的Openi开发环境
  2. oneapm服务器性能调优,中塑在线 · OneAPM 优化应用性能管理,提升用户体验 - OneAPM 电商类客户案例...
  3. 胶带机 高速分散机设计 轴流风机 带式运输机的驱动装置图 选粉机 焊缝机三维图 链式输送机结构图 喷漆室双链机图 焊接变位机…设计
  4. 页面停留时间和网站停留时间详解
  5. Python 课程设计 ---- 高校教务系统(数据库、wxPython界面)
  6. UAP开发步骤详解(很详细)--③单据向导完善
  7. 数字化用户杂志数字化用户杂志社数字化用户编辑部2023年第14期目录
  8. Vivado从此开始(进阶篇)读书笔记——跨时钟处理
  9. 在ROS中兼容Python3和Python2
  10. Vue——简易计算器