没有真正实践过的朋友,总是认为把HashMap
改为ConcurrentHashMap
,就完美解决并发了呀。或者使用写时复制的CopyOnWriteArrayList
,性能更佳呀!技术言论虽然自由。
但面对魔鬼面试官时,我们更在乎的是这些真的正确吗?
生产环境中,有时获取到的用户信息是别人的。查看代码后,发现是使用了ThreadLocal
缓存获取到的用户信息。
ThreadLocal
适用于变量在线程间隔离,而在方法或类间共享的场景。若用户信息的获取比较昂贵(比如从DB查询),则在ThreadLocal
中缓存比较合适。问题来了,为什么有时会出现用户信息错乱?
使用ThreadLocal
存放一个Integer
值,代表需要在线程中保存的用户信息,初始null
。先从ThreadLocal
获取一次值,然后把外部传入的参数设置到ThreadLocal
中,模拟从当前上下文获取用户信息,随后再获取一次值,最后输出两次获得的值和线程名称。
固定思维认为,在设置用户信息前第一次获取的值始终是null
,但要清楚程序运行在Tomcat
,执行程序的线程是Tomcat
的工作线程,其基于线程池。而线程池会重用固定线程,一旦线程重用,那么很可能首次从ThreadLocal
获取的值是之前其他用户的请求遗留的值。这时,ThreadLocal
中的用户信息就是其他用户的信息。
在配置文件设置Tomcat
参数-工作线程池最大线程数设为1,这样始终是同一线程在处理请求:
server.tomcat.max-threads=1
先让用户1请求接口,第一、第二次获取到用户ID分别是null和1,符合预期。
用户2请求接口,bug复现
!第一、第二次获取到用户ID分别是1和2,显然第一次获取到了用户1的信息,因为Tomcat线程池重用了线程。两次请求线程都是同一线程:http-nio-45678-exec-1
。
写业务代码时,首先要理解代码会跑在什么线程上:
在finally
代码块显式清除ThreadLocal
中数据。即使新请求过来,使用了之前的线程,也不会获取到错误的用户信息。修正后代码:
ThreadLocal
利用独占资源的解决线程安全问题,若就是要资源在线程间共享怎么办?
就需要用到线程安全的容器
。
使用了线程安全的并发工具,并不代表解决了所有线程安全问题。
current()
的时候初始化一个初始化种子到线程,每次nextseed
再使用之前的种子生成新的种子:
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
如果你通过主线程调用一次current
生成一个ThreadLocalRandom
实例保存,那么其它线程来获取种子的时候必然取不到初始种子,必须是每一个线程自己用的时候初始化一个种子到线程。可以在nextSeed设置一个断点看看:
UNSAFE.getLong(Thread.currentThread(),SEED);
我们都知道ConcurrentHashMap
是个线程安全的哈希表容器,但它仅保证提供的原子性读写操作线程安全。
有个含900个元素的Map,现在再补充100个元素进去,这个补充操作由10个线程并发进行。
开发人员误以为使用ConcurrentHashMap
就不会有线程安全问题,于是不加思索地写出了下面的代码:在每一个线程的代码逻辑中先通过size
方法拿到当前元素数量,计算ConcurrentHashMap
目前还需要补充多少元素,并在日志中输出了这个值,然后通过putAll
方法把缺少的元素添加进去。
为方便观察问题,我们输出了这个Map一开始和最后的元素个数。
访问接口:
分析日志输出可得:
worker13
线程查询到当前需要填充的元素为49,还不是100的倍数HashMap
的总项目数是1549,也不符合填充满1000的预期ConcurrentHashMap
就像是一个大篮子,现在这个篮子里有900个桔子,我们期望把这个篮子装满1000个桔子,也就是再装100个桔子。有10个工人来干这件事儿,大家先后到岗后会计算还需要补多少个桔子进去,最后把桔子装入篮子。
ConcurrentHashMap
这篮子本身,可以确保多个工人在装东西进去时,不会相互影响干扰,但无法确保工人A看到还需要装100个桔子但是还未装时,工人B就看不到篮子中的桔子数量。你往这个篮子装100个桔子的操作不是原子性的,在别人看来可能会有一个瞬间篮子里有964个桔子,还需要补36个桔子。
ConcurrentHashMap
对外提供能力的限制:
containsValue
等聚合方法,在并发下可能会反映ConcurrentHashMap
的中间状态。因此在并发情况下,这些方法的返回值只能用作参考,而不能用于流程控制。显然,利用size方法计算差异值,是一个流程控制整段逻辑加锁:
只有一个线程查询到需补100个元素,其他9个线程查询到无需补,最后Map大小1000。
既然使用ConcurrentHashMap
还要全程加锁,还不如使用HashMap
呢?不完全是这样。
ConcurrentHashMap
提供了一些原子性的简单复合逻辑方法,用好这些方法就可以发挥其威力。这就引申出代码中常见的另一个问题:在使用一些类库提供的高级工具类时,开发人员可能还是按照旧的方式去使用这些新类,因为没有使用其真实特性,所以无法发挥其威力。
使用Map来统计Key出现次数的场景。
ConcurrentHashMap
来统计,Key的范围是10] show me code
:有了上节经验,我们这直接锁住Map,再做
这段代码在功能上的确毫无没有问题,但却无法充分发挥ConcurrentHashMap
的性能,优化后:
ConcurrentHashMap
的原子性方法computeIfAbsent
做复合逻辑操作,判断K是否存在V,若不存在,则把Lambda运行后结果存入Map作为V,即新创建一个LongAdder对象,最后返回V
因为computeIfAbsent
返回的V是LongAdder
,是个线程安全的累加器,可直接调用其increment
累加。
这样在确保线程安全的情况下达到极致性能,且代码行数骤减。
使用StopWatch
测试两段代码的性能,最后的断言判断Map中元素的个数及所有V
的和是否符合预期来校验代码正确性
性能测试结果:
比使用锁性能提升至少5倍。
Java的Unsafe
实现的CAS
。
它在JVM层确保写入数据的原子性,比加锁效率高:
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSetObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
所以不要以为只要用了ConcurrentHashMap
并发工具就是高性能的高并发程序。
再比如一段简单的非 DB操作的业务逻辑,时间消耗却超出预期时间,在修改数据时操作本地缓存比回写DB慢许多。原来是有人使用了CopyOnWriteArrayList
缓存大量数据,而该业务场景下数据变化又很频繁。
CopyOnWriteArrayList
虽然是一个线程安全版的ArrayList,但其每次修改数据时都会复制一份数据出来,所以只适用读多写少或无锁读场景。
所以一旦使用CopyOnWriteArrayList
,一定是因为场景适宜而非炫技。
测试并发写性能:
测试结果:高并发写,CopyOnWriteArray
比同步ArrayList
慢百倍:
测试并发读性能:
测试结果:高并发读(100万次get操作),CopyOnWriteArray
比同步ArrayList
快24倍:
高并发写时,CopyOnWriteArrayList
为何这么慢呢?因为其每次add时,都用Arrays.copyOf
创建新数组,频繁add时内存申请释放性能消耗大。
参考:http://e9ew9.cn/eReTs
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/_P7ejizofkaCrf3QX0UDDw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。