面试小结并发篇

关于BlockingQueue和TransferQueue的异同。

  • TransferQueue继承了BlockingQueue并扩展了一些新方法。BlockingQueue是Java 5中加入的接口,它是指这样的一个队列:当生产者向队列添加元素但队列已满时,生产者会被阻塞;当消费者从队列移除元素但队列为空时,消费者会被阻塞。
  • TransferQueue则更进一步,生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费(不只仅是添加到队列里就完事),新添加的transfer方法用来实现这种约束。顾名思义,阻塞就是发生在元素从一个线程transfer到另外一个线程的过程当中,它有效地实现了元素在线程之间的传递(以创建Java内存模型中的happens-before关系的方式)。
  • TransferQueue还包括了其余的一些方法:两个tryTransfer方法,一个是非阻塞的,另外一个带有timeout参数设置超时时间的。还有两个辅助方法hasWaitingConsumer()和getWaitingConsumerCount()。
  • TransferQueue相比SynchronousQueue用处更广、更好用,由于你能够决定是使用BlockingQueue的方法(例如put方法)仍是确保一次传递完成(即transfer方法)。在队列中已有元素的状况下,调用transfer方法,能够确保队列中被传递元素以前的全部元素都能被处理。Doug Lea说从功能角度来说,LinkedTransferQueue其实是ConcurrentLinkedQueue、SynchronousQueue(公平模式)和LinkedBlockingQueue的超集。并且LinkedTransferQueue更好用,由于它不只仅综合了这几个类的功能,同时也提供了更高效的实现。

使用场景:当咱们不想生产者过分生产消息时,TransferQueue可能很是有用,可避免发生OutOfMemory错误。在这样的设计中,消费者的消费能力将决定生产者产生消息的速度。java

https://segmentfault.com/a/1190000011266361node

http://blog.csdn.net/yjian2008/article/details/16951811算法

 

1.8中ConcurrentHashMap的实现原理。

  • ConcurrentHashMap在jdk1.8中主要作了2方面的改进:改进一是取消segments字段,直接采用transient volatile HashEntry[] table保存数据,采用table数组元素做为锁,从而实现了对每一行数据进行加锁,进一步减小并发冲突的几率;改进二是将原先table数组+单向链表的数据结构,变动为table数组+单向链表+红黑树的结构,对于hash表来讲,最核心的能力在于将key hash以后能均匀的分布在数组中,若是hash以后散列的很均匀,那么table数组中的每一个队列长度主要为0或者1。但实际状况并不是老是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,可是在数据量过大或者运气不佳的状况下,仍是会存在一些队列长度过长的状况,若是仍是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);所以,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度能够下降到O(logN),能够改进性能。
  • TreeNode类:树节点类,另一个核心的数据结构。当链表长度过长的时候,会转换为TreeNode。可是与HashMap不相同的是,它并非直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。并且TreeNode在ConcurrentHashMap继承自Node类,而并不是HashMap中的集成自LinkedHashMap.Entry。
  • 二叉查找树,也称有序二叉树(ordered binary tree),是指一棵空树或者具备下列性质的二叉树:
1.若任意节点的左子树不空,则左子树上全部结点的值均小于它的根结点的值;
2.若任意节点的右子树不空,则右子树上全部结点的值均大于它的根结点的值;
3.任意节点的左、右子树也分别为二叉查找树。
4.没有键值相等的节点(no duplicate nodes)。
  • 红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增长了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。但它是如何保证一棵n个结点的红黑树的高度始终保持在logn的呢?这就引出了红黑树的5个性质:
1.每一个结点要么是红的要么是黑的。  
2.根结点是黑的。  
3.每一个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。  
4.若是一个结点是红的,那么它的两个儿子都是黑的。  
5.对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。

 

    在扩充HashMap的时候,不须要像JDK1.7的实现那样从新计算hash,只须要看看原来的hash值新增的那个bit是1仍是0就行了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。这个设计确实很是的巧妙,既省去了从新计算hash值的时间,并且同时,因为新增的1bit是0仍是1能够认为是随机的,所以resize的过程,均匀的把以前的冲突的节点分散到新的bucket了。segmentfault

 

Java8对读写锁的改进:StampedLock

 

读不阻塞写的实现思路:数组

        在读的时候若是发生了写,则应当重读而不是在读的时候直接阻塞写!服务器

        使用StampedLock就能够实现一种无障碍操做,即读写之间不会阻塞对方,可是写和写之间仍是阻塞的!数据结构

http://blog.csdn.net/sunfeizhi/article/details/52135136多线程

 

什么是一致性哈希?

  • 环形Hash空间:按照经常使用的hash算法来将对应的key哈希到一个具备2^32次方个桶的空间中,即0~(2^32)-1的数字空间中。如今咱们能够将这些数字头尾相连,想象成一个闭合的环形;
  • 把数据经过必定的hash算法处理后映射到环上;
  • 将机器经过hash算法映射到环上(通常状况下对机器的hash计算是采用机器的IP或者机器惟一的别名做为输入值),而后以顺时针的方向计算,将全部对象存储到离本身最近的机器中;
  • 机器的删除与添加:普通hash求余算法最为不妥的地方就是在有机器的添加或者删除以后会照成大量的对象存储位置失效,这样就大大的不知足单调性了。经过对节点的添加和删除的分析,一致性哈希算法在保持了单调性的同时,仍是数据的迁移达到了最小,这样的算法对分布式集群来讲是很是合适的,避免了大量数据迁移,减少了服务器的的压力。
  • 平衡性:在一致性哈希算法中,为了尽量的知足平衡性,其引入了虚拟节点。它其实是节点在hash空间的复制品,一实际个节点对应了若干个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在hash空间中以hash值排列。

 

AtomicLong、LongAdder和LongAccumulator的实现有何不一样?

  • AtomicLong是Java 5引入的基于CAS的无锁的操做长整形值的工具类;
    • AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均经过CAS 指令从机器指令级别操做保证并发的原子性。AtomicLong这种经过乐观锁的方式相对于加锁的机制已是很高效了,可是在高并发场景下,乐观锁失败会经过自旋不断尝试进行CAS操做,直到更新成功,这一般会进一步恶化竞争场景。并发

  • LongAdder是Java 8提供的累加器,基于Striped64实现。它经常使用于状态采集、统计等场景。AtomicLong也能够用于这种场景,但在线程竞争激烈的状况下,LongAdder要比AtomicLong拥有更高的吞吐量,但会耗费更多的内存空间。
    • LongAdder用多个cell的值的sum来表示一个long型数据。相似锁分段的思想,在有并发冲突的场景下,每一个线程更新一个cell,经过不一样线程修改各自的cell,来下降并发竞争。当每一个cell的更新还有冲突时,进行cell扩容,来进一步下降竞争。cell的个数不是无限大的,和cpu的核数有关。app

      想要获取LongAdder表示的数据时,经过sum来计算base和每一个cell的值的和。

    • LongAdder给了咱们一个很是容易想到的解决方案:减小并发,将单一value的更新压力分担到多个value中去,下降单个value的 “热度”,分段更新!惟一会制约AtomicLong高效的缘由是高并发,高并发意味着CAS的失败概率更高, 重试次数更多,越多线程重试,CAS失败概率又越高,变成恶性循环,AtomicLong效率下降。Cell数组中的每一项就是一个段,当咱们须要获得值时,将全部cell中的数据和base相加就能够了。

  • Striped64的设计核心思路就是经过内部的分散计算来避免竞争(好比多线程CAS操做时的竞争)。Striped64内部包含一个基础值和一个单元哈希表。没有竞争的状况下,要累加的数会累加到这个基础值上;若是有竞争的话,会将要累加的数累加到单元哈希表中的某个单元里面。因此整个Striped64的值包括基础值和单元哈希表中全部单元的值的总和。
 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

 

/**

* 存放Cell的hash表,大小为2的幂。

*/

transient volatile Cell[] cells;

/**

* 基础值,没有竞争时会使用(更新)这个值,同时作为初始化竞争失败的回退方案。

* 原子更新。

*/

transient volatile long base;

/**

* 自旋锁,经过CAS操做加锁,用于保护建立或者扩展Cell表。

*/

transient volatile int cellsBusy;

  • LongAccumulator和LongAdder相似,也基于Striped64实现。但要比LongAdder更加灵活(要传入一个函数接口),LongAdder至关因而LongAccumulator的一种特例。

 

http://ginobefunny.com/post/java_concurrent_interview_questions/

相关文章
相关标签/搜索