惊:FastThreadLocal吞吐量居然是ThreadLocal的3倍!!!

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://jiangxinlingdu.blog.csdn.net/article/details/94495469

说明

接着上次手撕面试题ThreadLocal!!!面试官一听,哎呦不错哦!本文将继续上文的话题,来聊聊FastThreadLocal,目前关于FastThreadLocal的很多文章都有点老有点过时了(本文将澄清几个误区),很多文章关于FastThreadLocal介绍的也不全,希望本篇文章可以带你彻底理解FastThreadLocal!!!

FastThreadLocal是Netty提供的,在池化内存分配等都有涉及到!​

关于FastThreadLocal,零度准备从这几个方面进行讲解:

  • FastThreadLocal的使用。
  • FastThreadLocal并不是什么情况都快,你要用对才会快。
  • FastThreadLocal利用字节填充来解决伪共享问题。
  • FastThreadLocal比ThreadLocal快,并不是空间换时间。
  • FastThreadLocal不在使用ObjectCleaner处理泄漏,必要的时候建议重写onRemoval方法。
  • FastThreadLocal为什么快?

FastThreadLocal的使用

FastThreadLocal用法上兼容ThreadLocal

FastThreadLocal使用示例代码:

public class FastThreadLocalTest {
    private static FastThreadLocal<Integer> fastThreadLocal = new FastThreadLocal<>();

    public static void main(String[] args) {

        //if (thread instanceof FastThreadLocalThread) 使用FastThreadLocalThread更优,普通线程也可以
        new FastThreadLocalThread(() -> {
            for (int i = 0; i < 100; i++) {
                fastThreadLocal.set(i);
                System.out.println(Thread.currentThread().getName() + "====" + fastThreadLocal.get());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "fastThreadLocal1").start();


        new FastThreadLocalThread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "====" + fastThreadLocal.get());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "fastThreadLocal2").start();
    }
}

代码截图:

代码运行结果:

我们在回顾下之前的ThreadLocal的 最佳实践做法:

try {
    // 其它业务逻辑
} finally {
    threadLocal对象.remove();
}

备注: 通过上面的例子,我们发现FastThreadLocal和ThreadLocal在用法上面基本差不多,没有什么特别区别,个人认为,这就是FastThreadLocal成功的地方,它就是要让用户用起来和ThreadLocal没啥区别,要兼容!

使用FastThreadLocal居然不用像ThreadLocal那样先try ………………… 之后finally进行threadLocal对象.remove();

由于构造FastThreadLocalThread的时候,通过FastThreadLocalRunnable对Runnable对象进行了包装:

FastThreadLocalRunnable.wrap(target)从而构造了FastThreadLocalRunnable对象。

FastThreadLocalRunnable在执行完之后都会调用FastThreadLocal.removeAll();

备注: FastThreadLocal不在使用ObjectCleaner处理泄漏,必要的时候建议重写onRemoval方法。关于这块将在本文后面进行介绍,这样是很多网上资料比较老的原因,这块已经去掉了。

如果是普通线程,还是应该最佳实践:

finally {
fastThreadLocal对象.removeAll();
}

注意: 如果使用FastThreadLocal就不要使用普通线程,而应该构建FastThreadLocalThread,关于为什么这样,关于这块将在本文后面进行介绍:FastThreadLocal并不是什么情况都快,你要用对才会快。

FastThreadLocal并不是什么情况都快,你要用对才会快

首先看看netty关于这块的测试用例:
代码路径:https://github.com/netty/netty/blob/4.1/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalFastPathBenchmark.java

备注: 在我本地进行测试,FastThreadLocal的吞吐量是jdkThreadLocal的3倍左右。机器不一样,可能效果也不一样,大家可以自己试试,反正就是快了不少。

关于ThreadLocal,之前的这篇:手撕面试题ThreadLocal!!!已经详细介绍了。

FastThreadLocal并不是什么情况都快,你要用对才会快!!!

注意: 使用FastThreadLocalThread线程才会快,如果是普通线程还更慢!
注意: 使用FastThreadLocalThread线程才会快,如果是普通线程还更慢!
注意: 使用FastThreadLocalThread线程才会快,如果是普通线程还更慢!

netty的测试目录下面有2个类:

  • FastThreadLocalFastPathBenchmark
  • FastThreadLocalSlowPathBenchmark

路径:https://github.com/netty/netty/blob/4.1/microbench/src/main/java/io/netty/microbench/concurrent/

FastThreadLocalFastPathBenchmark测试结果: 是ThreadLocal的吞吐量的3倍左右。

FastThreadLocalSlowPathBenchmark测试结果: 比ThreadLocal的吞吐量还低。

测试结论: 使用FastThreadLocalThread线程操作FastThreadLocal才会快,如果是普通线程还更慢!

注释里面给出了三点:

  • FastThreadLocal操作元素的时候,使用常量下标在数组中进行定位元素来替代ThreadLocal通过哈希和哈希表,这个改动特别在频繁使用的时候,效果更加显著!

  • 想要利用上面的特征,线程必须是FastThreadLocalThread或者其子类,默认DefaultThreadFactory都是使用FastThreadLocalThread的

  • 只用在FastThreadLocalThread或者子类的线程使用FastThreadLocal才会更快,因为FastThreadLocalThread 定义了属性threadLocalMap类型是InternalThreadLocalMap。如果普通线程会借助ThreadLocal。

我们看看NioEventLoopGroup细节:

看到这里,和刚刚我们看到的注释内容一致的,是使用FastThreadLocalThread的。

netty里面使用FastThreadLocal的举例常用的:

池化内存分配:

会使用到Recycler

而Recycler也使用了FastThreadLocal

我们再看看看测试类:

备注: 我们会发现FastThreadLocalFastPathBenchmark里面的线程是FastThreadLocal。

备注: 我们会发现FastThreadLocalSlowPathBenchmark里面的线程 不是FastThreadLocal

FastThreadLocal只有被的线程是FastThreadLocalThread或者其子类使用的时候才会更快,吞吐量我这边测试的效果大概3倍左右,但是如果是普通线程操作FastThreadLocal其吞吐量比ThreadLocal还差!

FastThreadLocal利用字节填充来解决伪共享问题

关于CPU 缓存 内容来源于美团:https://tech.meituan.com/2016/11/18/disruptor.html

下图是计算的基本结构。L1、L2、L3分别表示一级缓存、二级缓存、三级缓存,越靠近CPU的缓存,速度越快,容量也越小。所以L1缓存很小但很快,并且紧靠着在使用它的CPU内核;L2大一些,也慢一些,并且仍然只能被一个单独的CPU核使用;L3更大、更慢,并且被单个插槽上的所有CPU核共享;最后是主存,由全部插槽上的所有CPU核共享。

img

当CPU执行运算的时候,它先去L1查找所需的数据、再去L2、然后是L3,如果最后这些缓存中都没有,所需的数据就要去主内存拿。走得越远,运算耗费的时间就越长。所以如果你在做一些很频繁的事,你要尽量确保数据在L1缓存中。

另外,线程之间共享一份数据的时候,需要一个线程把数据写回主存,而另一个线程访问主存中相应的数据。

下面是从CPU访问不同层级数据的时间概念:

可见CPU读取主存中的数据会比从L1中读取慢了近2个数量级。

缓存行

Cache是由很多个cache line组成的。每个cache line通常是64字节,并且它有效地引用主内存中的一块儿地址。一个Java的long类型变量是8字节,因此在一个缓存行中可以存8个long类型的变量。

CPU每次从主存中拉取数据时,会把相邻的数据也存入同一个cache line。

在访问一个long数组的时候,如果数组中的一个值被加载到缓存中,它会自动加载另外7个。因此你能非常快的遍历这个数组。事实上,你可以非常快速的遍历在连续内存块中分配的任意数据结构。

伪共享

由于多个线程同时操作同一缓存行的不同变量,但是这些变量之间却没有啥关联,但是每次修改,都会导致缓存的数据变成无效,从而明明没有任何修改的内容,还是需要去主存中读(CPU读取主存中的数据会比从L1中读取慢了近2个数量级)但是其实这块内容并没有任何变化,由于缓存的最小单位是一个缓存行,这就是伪共享。

如果让多线程频繁操作的并且没有关系的变量在不同的缓存行中,那么就不会因为缓存行的问题导致没有关系的变量的修改去影响另外没有修改的变量去读主存了(那么从L1中取是从主存取快2个数量级的)那么性能就会好很多很多。

有伪共享 和没有的情况的测试效果

代码路径:https://github.com/jiangxinlingdu/nettydemo

nettydemo

利用字节填充来解决伪共享,从而速度快了3倍左右。

FastThreadLocal使用字节填充解决伪共享

之前介绍ThreadLocal的时候,说过ThreadLocal是用在多线程场景下,那么FastThreadLocal也是用在多线程场景,大家可以看下这篇:手撕面试题ThreadLocal!!!,所以FastThreadLocal需要解决伪共享问题,FastThreadLocal使用字节填充解决伪共享。

这个是我自己手算的,通过手算太麻烦,推荐一个工具JOL

http://openjdk.java.net/projects/code-tools/jol/

[外链图片转存失败(img-IuveGvGF-1562114096756)

推荐IDEA插件:https://plugins.jetbrains.com/plugin/10953-jol-java-object-layout

代码路径:https://github.com/jiangxinlingdu/nettydemo

nettydemo

通过这个工具算起来就很容易了,如果以后有类似的需要看的,不用手一个一个算了。

FastThreadLocal被FastThreadLocalThread进行读写的时候也可能利用到缓存行

并且由于当线程是FastThreadLocalThread的时候操作FastThreadLocal是通过indexedVariables数组进行存储数据的的,每个FastThreadLocal有一个常量下标,通过下标直接定位数组进行读写操作,当有很多FastThreadLocal的时候,也可以利用缓存行,比如一次indexedVariables数组第3个位置数据,由于缓存的最小单位是缓存行,顺便把后面的4、5、6等也缓存了,下次刚刚好另外FastThreadLocal下标就是5的时候,进行读取的时候就直接走缓存了,比走主存可能快2个数量级。

一点疑惑

**问题:**为什么这里填充了9个long值呢???

我提了一个issue:https://github.com/netty/netty/issues/9284

虽然也有人回答,但是感觉不是自己想要的,说服不了自己!!!

FastThreadLocal比ThreadLocal快,并不是空间换时间

现在清理已经去掉,本文下面会介绍,所以FastThreadLocal比ThreadLocal快,并不是空间换时间,FastThreadLocal并没有浪费空间!!!

FastThreadLocal不在使用ObjectCleaner处理泄漏,必要的时候建议重写onRemoval方法

最新的netty版本中已经不在使用ObjectCleaner处理泄漏:

https://github.com/netty/netty/commit/9b1a59df383559bc568b891d73c7cb040019aca6#diff-e0eb4e9a6ea15564e4ddd076c55978de

https://github.com/netty/netty/commit/5b1fe611a637c362a60b391079fff73b1a4ef912#diff-e0eb4e9a6ea15564e4ddd076c55978de

去掉原因:

https://github.com/netty/netty/issues/8017

我们看看FastThreadLocal的onRemoval

如果使用的是FastThreadLocalThread能保证调用的,重写onRemoval做一些收尾状态修改等等


FastThreadLocal为什么快?

FastThreadLocal操作元素的时候,使用常量下标在数组中进行定位元素来替代ThreadLocal通过哈希和哈希表,这个改动特别在频繁使用的时候,效果更加显著!计算该ThreadLocal需要存储的位置是通过hash算法确定位置:
int i = key.threadLocalHashCode & (len-1);而FastThreadLocal就是一个常量下标index,这个如果执行次数很多也是有影响的。

并且FastThreadLocal利用缓存行的特性,FastThreadLocal是通过indexedVariables数组进行存储数据的,如果有多个FastThreadLocal的时候,也可以利用缓存行,比如一次indexedVariables数组第3个位置数据,由于缓存的最小单位是缓存行,顺便把后面的4、5、6等也缓存了,下次刚刚好改线程需要读取另外的FastThreadLocal,这个FastThreadLocal的下标就是5的时候,进行读取的时候就直接走缓存了,比走主存可能快2个数量级而ThreadLocal通过hash是分散的。

展开阅读全文

惊人大秘密: 中国人必看3

12-03

日本的民族性和地域特点,决定了中日两国的战略利益肯定是相冲突的,而且还有钓rn鱼岛这个导火索。未来两国究竟是战略合作,还是相互的战略对手,还很难说。就日本的民rn族性而言,绝对不会屈从于中国的压力。而日本成为军事正常化国家已经成为必然。日中之rn间的争端,从经济到 再到地缘战略,会越来越突显。所以在战略上打压日本是必然要做的事rn情。 rn    而中国这个订单,比全日本铁路总长度加起来还要多好几倍。对于四五百亿对于经营rn上面临重大压力的三菱重工,无疑是雪中送炭。也就等于挽救了三菱重工,这个日本的重工rn业支柱性的企业。而三菱重工则是日本右翼团体主要的资助方。从种种角度考虑,我们都不rn应该把这项计划包给日本人做。 rn    rn    当然,目前日本方面还有优势。首先是在成本和安全性方面超过法、德对手。而日本rn成功的新干线控制技术也是我们所需要的。而且,日本方面可以先提供部分贷款。条件诱人rn,但仔细考虑一下。日本人的算盘也精得很,肯定会向中国索取若干年的铁路管理和运营权rn,然后再进行技术转让,在此期间因为他们的控制技术是中国人所掌握不了的,所以这就等rn于控制住了中国北京到上海的大动脉。若干年后再进行技术转让,那时的这种技术我们是否rn需要也未可知。 rn    rn    小泉前段时间说,给中国的经济援助中国人不知道,中国人民也不领情,让日本人民rn伤心了。其实,中国并不是日本最大的经援国。他在这个时候说这样的话,无疑就是在暗示rn中国,可能会将京沪铁路建设权与日本给中国的经援捆绑到一起。想给中国来个敲山震虎。rn    我希望中国的高层要看清本质,坚决顶住。说实话,日本给中国的经援,是中日两国rn表面上友好的支柱之一,日本方面不能,也不敢砍断了这根柱子。主要就是想吓吓中国人,rn然后给他们在中国的几个主要经济项目打出政府牌罢了。 rn    rn    而目前的情况好像不太乐观,中国高层好像更倾向于使用日本新干线技术,这是一种rn在战略上短视的行为。也不符合目前中国应该尊旬的远交近攻的战略思想。希望胡**、温总rn理能够看到这个建议。再让京沪铁路招标的中国高层的具体负责认真的、全盘的考虑一下。rn然后再做定论为好 rn    rn    可是当我翻开资料后,却发现一个很可怕的问题,资料中提到”架设新干线的首要条rn件是必 须对新干线所经过的城市,地区,进行彻底的地质勘探”在往后翻我发现,这些资料rn对东京到大阪的东海新干线所经过的地区,乃至周围的地域都进行了详细地分析。线路经过rn的地点,精确到厘米或更细,周围的地域精确到分米,对整个日本东海地区大到山脉、河床rn;小到下水道、煤气管道、电信设备;下到地下近千米的地层分析、地质构造,上到该地区rn的温度、湿度,里到沿途人工隧道,天然洞穴,外到植 被分布,人口密度等等,无一不精确rn标示。 rn    看到这里我觉得日本人的”认真”二字就够咱们中国人学习好久了。这还只是日本公rn布的资料啊,没有公布的一定比这详细百倍。可是仅仅看到这里我就已经是盛夏里出了一身rn冷汗!!中国的高铁能让日本进行建造吗? rn    rn    如果与日本进行合作,必然要向日本提供地质报告,中国可能还不具备如此详细的资rn料,那就有可能让日本对中国的华北华东地区进行全面的地质地理勘探,即便不由日本勘探rn,那也必须向日方提供详细资料,如果这样的话,日本不就对中国华东华北地区的地理,地rn质,人口,工业,环境,军事情报等等了如指掌了吗?这样一来,两国如果世世代代和平相rn处倒也罢了,可是万一两国交恶,发生战事了那中国如何是好,哪怕中日没有直接冲突 ,万rn一日本把资料提供给中国的敌对国了怎么办。这其实就相当于把祖国的大好河山拱手送人啊rn!到那时,天堑何以为天堑,地势无以为地势,天时地利将尽始,敌人对中国的侵略只会如rn虎添翼,势如破竹啊! 这并不是保守,不是闭关锁国,而为那死去的3000万同胞!!! rn    7、回顾中日历史,与其说是一衣带水,倒不如说是一衣带血倒更为贴切。自龙翔三rn年 (公元663年唐高宗时代),日本占领朝鲜锦江口始,中日间就拉开了兵戈相见的 序幕(rn至于那个携大量珍贵文物东渡的汉奸和尚鉴真,不明白历史书上为什么还要 对他大加褒扬)rn,明太祖洪武年,肆虐达七十八年之久。万历年,丰臣秀吉出 兵二十一万,攻占朝鲜,明大rn将李如松出兵援朝,在付出较大牺牲后一举击败日本 。然而。从那时起,日本的战略家们就rn已经开始处心积虑地研究和制订灭亡中国的 计划。到了近代,甲午战争的惨败彻底暴露了老rn大帝国的颓弱本质,倭寇的帝国梦 ,就是那时开始于中华民族的血泊与白骨之中的。 rn    56年前的8月6日和8月9日,曾经不可一世的日本在屡受挫败后,遭到的真正的 天谴rn。上帝说:伸冤在我,我必报应。果然,那动人心魄的一瞬间的闪光与冲天而 起的蘑菇云,rn让日本人第一次感到了死亡的恐惧。但今天,当废墟再一次成为繁华的大都市的时候,我们rn看到的是:今天的日本人,爪牙更锋利,气焰更嚣张,而记 忆却更差了。在一部叫《漂流街rn》的电影里,一个爱打乒乓球的中国黑帮老大,曾对日本人说过这样一段话:从前,一个岛rn上生活着一群猿猴,后来有中国人去教他 们写字,猿猴们学会了,但却不懂得字的含义,不rn懂得什么叫诚实,什么叫善良, 什么叫认罪……(大意)rnrnrn日本人聪明、勇敢、坚忍、团结,但他们更变态、畸形、极端、怪异(这些贬 rn义词并非凭空捏造,如果你领略过那些深受日本人喜爱的病态的艺术,你就会感受 到,日本rn人性最深处的丑陋与邪恶),这样一群有着人的思维的高级禽兽,要远比头脑简单,只是知rn道嗜血的豺狼可怕的多。 rn    rn      中华民族,不是狭隘的种族,在她的怀抱中,56个民族能够亲密无间,和睦相 rn处;中国人,更不是狭隘的民族主义者,我们在全世界有这数不清的不同语言,不 同肤色的rn朋友。但唯一的例外,就是太平洋上的四个小岛,那里生活着地球上最凶残与可怕的动物,rn中华民族永远的心腹之患。中日世仇,不共戴天!每一个中国人 永远都不应该忘记日本曾经rn对我们做过什么!对于一个应该在地球上消失的种群, 对于一个人人可以诛之的国家,对于rn一个极端变态的民族,我们永远也不要再对他 们抱有丝毫幻想。抗战期间的一首小诗,叫做rn《假如我们不去打仗》:假如我们不 去打仗/那么敌人杀死了我们/还要用刺刀指着我们的骨rn头说/看哪,这是奴隶!   rn    rn    中日之间,没有任何友谊可言,有的只有连绵千年的仇恨!先辈的英灵,每天 都在rn半空中凝视我们这些至今依然无所作为的不肖的子孙,他们焦虑,他们失望… …但我们忍耐rn终将会有尽头,厉兵秣马,做好准备吧,犯强汉者,虽远必诛!浑浊的小泉,永远也别想再rn一次兴风作浪!“巨浪”和“东风”就是我们的怒吼,我们 期待着,铭刻着中国制造的蘑菇rn云再次生起,在东京,在京都,在大版,在每一个 孳生着 量艿 巢穴。中华民族的千年梦魇rn,永远沉入太平洋底,累劫不复超生!也 许很快,也许还要等很久,但血债必用血来!   rn  大直若屈,大巧若拙,大辩若讷rn 论坛

没有更多推荐了,返回首页