Java核心技术面试精讲
杨晓峰
前Oracle首席工程师
立即订阅
43250 人已学习
课程目录
已完结 43 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 以面试题为切入点,有效提升你的Java内功
免费
模块一 Java基础 (14讲)
第1讲 | 谈谈你对Java平台的理解?
第2讲 | Exception和Error有什么区别?
第3讲 | 谈谈final、finally、 finalize有什么不同?
第4讲 | 强引用、软引用、弱引用、幻象引用有什么区别?
第5讲 | String、StringBuffer、StringBuilder有什么区别?
第6讲 | 动态代理是基于什么原理?
第7讲 | int和Integer有什么区别?
第8讲 | 对比Vector、ArrayList、LinkedList有何区别?
第9讲 | 对比Hashtable、HashMap、TreeMap有什么不同?
第10讲 | 如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全?
第11讲 | Java提供了哪些IO方式? NIO如何实现多路复用?
第12讲 | Java有几种文件拷贝方式?哪一种最高效?
第13讲 | 谈谈接口和抽象类有什么区别?
第14讲 | 谈谈你知道的设计模式?
模块二 Java进阶 (16讲)
第15讲 | synchronized和ReentrantLock有什么区别呢?
第16讲 | synchronized底层如何实现?什么是锁的升级、降级?
第17讲 | 一个线程两次调用start()方法会出现什么情况?
第18讲 | 什么情况下Java程序会产生死锁?如何定位、修复?
第19讲 | Java并发包提供了哪些并发工具类?
第20讲 | 并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?
第21讲 | Java并发类库提供的线程池有哪几种? 分别有什么特点?
第22讲 | AtomicInteger底层实现原理是什么?如何在自己的产品代码中应用CAS操作?
第23讲 | 请介绍类加载过程,什么是双亲委派模型?
第24讲 | 有哪些方法可以在运行时动态生成一个Java类?
第25讲 | 谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError?
第26讲 | 如何监控和诊断JVM堆内和堆外内存使用?
第27讲 | Java常见的垃圾收集器有哪些?
第28讲 | 谈谈你的GC调优思路?
第29讲 | Java内存模型中的happen-before是什么?
第30讲 | Java程序运行在Docker等容器环境有哪些新问题?
模块三 Java安全基础 (2讲)
第31讲 | 你了解Java应用开发中的注入攻击吗?
第32讲 | 如何写出安全的Java代码?
模块四 Java性能基础 (3讲)
第33讲 | 后台服务出现明显“变慢”,谈谈你的诊断思路?
第34讲 | 有人说“Lambda能让Java程序慢30倍”,你怎么看?
第35讲 | JVM优化Java代码时都做了什么?
模块5 Java应用开发扩展 (4讲)
第36讲 | 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
第37讲 | 谈谈Spring Bean的生命周期和作用域?
第38讲 | 对比Java标准NIO类库,你知道Netty是如何实现更高性能的吗?
第39讲 | 谈谈常用的分布式ID的设计方案?Snowflake是否受冬令时切换影响?
周末福利 (2讲)
周末福利 | 谈谈我对Java学习和面试的看法
周末福利 | 一份Java工程师必读书单
结束语 (1讲)
结束语 | 技术没有终点
Java核心技术面试精讲
登录|注册

第10讲 | 如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全?

杨晓峰 2018-05-26
我在之前两讲介绍了 Java 集合框架的典型容器类,它们绝大部分都不是线程安全的,仅有的线程安全实现,比如 Vector、Stack,在性能方面也远不尽如人意。幸好 Java 语言提供了并发包(java.util.concurrent),为高度并发需求提供了更加全面的工具支持。
今天我要问你的问题是,如何保证容器是线程安全的?ConcurrentHashMap 如何实现高效地线程安全?

典型回答

Java 提供了不同层面的线程安全支持。在传统集合框架内部,除了 Hashtable 等同步容器,还提供了所谓的同步包装器(Synchronized Wrapper),我们可以调用 Collections 工具类提供的包装方法,来获取一个同步的包装容器(如 Collections.synchronizedMap),但是它们都是利用非常粗粒度的同步方式,在高并发情况下,性能比较低下。
另外,更加普遍的选择是利用并发包提供的线程安全容器类,它提供了:
各种并发容器,比如 ConcurrentHashMap、CopyOnWriteArrayList。
各种线程安全队列(Queue/Deque),如 ArrayBlockingQueue、SynchronousQueue。
各种有序容器的线程安全版本等。
具体保证线程安全的方式,包括有从简单的 synchronize 方式,到基于更加精细化的,比如基于分离锁实现的 ConcurrentHashMap 等并发实现等。具体选择要看开发的场景需求,总体来说,并发包内提供的容器通用场景,远优于早期的简单同步实现。

考点分析

谈到线程安全和并发,可以说是 Java 面试中必考的考点,我上面给出的回答是一个相对宽泛的总结,而且 ConcurrentHashMap 等并发容器实现也在不断演进,不能一概而论。
如果要深入思考并回答这个问题及其扩展方面,至少需要:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java核心技术面试精讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(56)

  • 明翼
    1.7
    put加锁
    通过分段加锁segment,一个hashmap里有若干个segment,每个segment里有若干个桶,桶里存放K-V形式的链表,put数据时通过key哈希得到该元素要添加到的segment,然后对segment进行加锁,然后在哈希,计算得到给元素要添加到的桶,然后遍历桶中的链表,替换或新增节点到桶中

    size
    分段计算两次,两次结果相同则返回,否则对所以段加锁重新计算


    1.8
    put CAS 加锁
    1.8中不依赖与segment加锁,segment数量与桶数量一致;
    首先判断容器是否为空,为空则进行初始化利用volatile的sizeCtl作为互斥手段,如果发现竞争性的初始化,就暂停在那里,等待条件恢复,否则利用CAS设置排他标志(U.compareAndSwapInt(this, SIZECTL, sc, -1));否则重试
    对key hash计算得到该key存放的桶位置,判断该桶是否为空,为空则利用CAS设置新节点
    否则使用synchronize加锁,遍历桶中数据,替换或新增加点到桶中
    最后判断是否需要转为红黑树,转换之前判断是否需要扩容

    size
    利用LongAdd累加计算
    2018-07-04
    1
    83
  • 徐金铎
    需要注意的一点是,1.8以后的锁的颗粒度,是加在链表头上的,这个是个思路上的突破。

    作者回复: 是的

    2018-05-26
    2
    62
  • 雷霹雳的爸爸
    今天这个纯粹知识盲点,纯赞,源码也得不停看
    2018-05-26
    27
  • Sean
    最近用ConcurrentHashMap的场景是,由于系统是一个公共服务,全程异步处理。最后一环节需要http rest主动响应接入系统,于是为了定制化需求,利用netty写了一版异步http clinet。其在缓存tcp链接时用到了。
    看到下面有一位朋友说起了自旋锁和偏向锁。
    自旋锁个人理解的是cas的一种应用方式。并发包中的原子类是典型的应用。
    偏向锁个人理解的是获取锁的优化。在ReentrantLock中用于实现已获取完锁的的线程重入问题。
    不知道理解的是否有误差。欢迎指正探讨。谢谢

    作者回复: 正确,互相交流
    偏向锁,侧重是低竞争场景的优化,去掉可能不必要的同步

    2018-05-28
    23
  • j.c.
    期待unsafe和cas的文章
    2018-05-26
    20
  • t
    对于我这种菜鸟来说,应该来一期讲讲volatile😭
    2018-07-03
    19
  • 虞飞
    老师在课程里讲到同步包装类比较低效,不太适合高并发的场景,那想请教一下老师,在list接口的实现类中。在高并发的场景下,选择哪种实现类比较好?因为ArrayList是线程不安全的,同步包装类又很低效,CopyonwriteArrayList又是以快照的形式来实现的,在频繁写入数据的时候,其实也很低效,那这个类型该怎么选择比较好?

    作者回复: 目前并发list好像就那一个,我觉得不必拘泥于list,不还有queue之类,看场景需要的真是list吗

    2018-05-27
    8
  • Kyle
    之前用JavaFX做一个客户端IM工具的时候,我将拉来的未被读取的用户聊天信息用ConcurrentHashMap存储(同时异步存储到Sqlite),Key存放用户id,Value放未读取的聊天消息列表。因为我考虑到存消息和读消息是由两个线程并发处理的,这两个线程共同操作一个ConcurrentHashMap。可能是我没处理好,最后直到我离职了还有消息重复、乱序的问题。请问我这种应用场景有什么问题吗?
    2018-05-28
    5
  • coder王
    您说的synchronized被改进很多很多了,那么在我们平常使用中,就用这个synchronized完成一些同步操作是不是OK?😁

    作者回复: 通常是的,前提是JDK版本需要新一点

    2018-05-28
    1
    5
  • Answer
    Unsafe?
    2018-07-03
    4
  • shawn
    老师,什么只有bin为空的时候才使用cas,其他地方用synchronized 呢?
    2018-07-02
    4
  • mongo
    请教老师:putVal方法中,什么情况下会进入else if ((fh=f.hash) == MOVED)分支?是进行扩容的时候吗?nextTable是做什么用的?

    作者回复: 我理解是的,判断是个ForwardingNode,resize正在进行;
    nexttable是扩容时的临时过渡

    2018-05-26
    4
  • mongo
    请教老师:putVal方法的第二个if分支,为什么要用tabAt?我的认识里直接数组下标寻址tab[i=(n-1) & hash]也是一个原子操作,不是吗?tabAt里面的getObjectVolatle()方法跟直接用数组下标tab[i=(n-1) & hash]寻址有什么区别?

    作者回复: 这个有volatile load语义

    2018-05-26
    4
  • 约书亚
    这期内容太难,分寸不好把握
    看8的concurenthashmap源码感觉挺困难,网上的博文帮助也不大,尤其是扩容这部分(似乎文章中没提)
    求问杨大有没有什么窍门,或者有什么启发性的paper或文章?
    可以泛化成,长期对lock free实现多个状态修改的问题比较困惑,希望得到启发

    作者回复: 本文尽量梳理了相对比较容易理解的部分;扩容细节我觉得是个加分项,不是每个人都会在乎那么深入;窍门,可以考虑画图辅助理解,我是比较笨的类型,除了死磕,不会太多窍门……

    2018-05-26
    4
  • Xg huang
    这里有个地方想跟老师交流一下想法, 从文中"所以,ConcurrentHashMap 的实现是通过重试机制(RETRIES_BEFORE_LOCK,指定重试次数 2),来试图获得可靠值。如果没有监控到发生变化(通过对比 Segment.modCount),就直接返回,否则获取锁进行操作。" 可以看出, 在高并发的情况下, "size()" 方法只是返回"近似值", 而我的问题是: 既然只是一个近似值, 为啥要用这种"重试,分段锁" 的复杂做法去计算这个值? 直接在不加锁的情况下返回segment 的size 岂不是更简单? 我能理解jdk开发者想尽一切努力在高性能地返回最精确的数值, 但这个"精确" 度无法量化啊, 对于调用方来说,这个值依然是不可靠的啊. 所以, 在我看来,这种做法收益很小(可能是我也比较懒吧), 或者有些设计上的要点我没有领悟出来, 希望老师指点一下.

    作者回复: 这个是在代价可接受情况下,尽量准确,就像含金量90%和99.9%,99.999%,还是有区别的,虽然不是百分百

    2018-06-08
    2
  • Leiy
    我感觉jdk8就相当于把segment分段锁更细粒度了,每个数组元素就是原来一个segment,那并发度就由原来segment数变为数组长度?而且用到了cas乐观锁,所以能支持更高的并发,不知道我这种理解对吗?如果对的话,我就在想,为什么并发大神之前没想到这种,哈哈😄,恳请指正。谢谢

    作者回复: 基本正确,cas只用在部分场景;
    事后看容易啊,说比做容易,😄

    2018-05-29
    2
  • Hesher
    并发包用的很少,这一节内容的前置知识比较多,对于使用经验少的人来说貌似是有点难了。问题很好,正好可以见识一下各种使用场景,不过留言大部分是针对内容的难点提问,而真正回答问题的还没有出现。

    作者回复: 后面并发部分会详细分析

    2018-05-28
    2
  • 行者
    老师麻烦讲讲自旋锁,偏向锁的特点和区别吧,一直不太清楚。

    作者回复: 好,后面有章节

    2018-05-27
    2
  • 日光倾城
    initTable里面的Thread.yield什么场景下会返回呢
    2019-08-25
    1
  • bazindes
    老师的内容讲的丰富 深入浅出 希望提高一下朗读人的要求吧 每节课都感觉有读错的 英文读不准就不说了 互斥读成互拆听的实在是别扭

    作者回复: 哈,抱歉,我反馈一下,主播也辛苦,不一定是职业码农

    2018-06-26
    1
收起评论
56
返回
顶部