读写锁
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock r = lock.readLock();
ReentrantReadWriteLock.WriteLock w = lock.writeLock();
读锁不互斥
读写锁互斥
写锁互斥
注意事项
- 读锁不支持条件变量condition
- 重入时升级不支持,即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
- 重入时降级支持,即持有写锁的情况下获得读锁
并发工具类
Semaphore
信号量,用来限制能同时访问共享资源的线程上限
CountdownLatch
用来进行线程同步协作,等待所有线程完成倒计时
当计数为0时,主线程结束等待,运行结束
CyclicBarrier
循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置计数个数,每个线程执
行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足计数个数时,继续执行
当对任务循环调用时,CyclicBarrier不需要重新定义
线程池的线程数应与CyclicBarrier的计数一致
ConcurrentHashMap
死链
Hashmap原理
JDK8中后加加入的元素会放在链表尾部,JDK7中后加入的元素会放在链表头部
随着数组内元素越来越多,数组长度也会增长,因此在数组元素超过阈值时会进行扩容
案例:
在 Threads 面板选中 Thread-1 恢复运行
此时e=1,next=35
随后,1先被放入新表的桶下标1中,接着35被放入新表。
这时 Thread-0 还停在 594 处,即e=1,next=35,随后Thread-0开始扩容,1先被放入新表,e更新为35,因为此时新的链为(35)->(1)->null,所以next更新为1。死链产生。(1)->(35)->(1),新的链表翻转了,所以相当于头链连到了尾链上。
JDK 8 ConcurrentHashMap
重要属性和内部类
ForwardingNode
例如,原始哈希表中有如下元素
因为是线程安全的hashtable,防止其他线程对线程搬迁工作(从后往前)产生影响,在对节点处理完后,会在其对应的下标下加一个ForwardingNode,其他线程访问发现是ForwardingNode,则说明已经处理过了,则不会在对链表做其它操作
ForwardingNode取值为负数
第二个作用是如果发现是ForwardingNode,则回去新的hash表中找那个key
TreeBin&TreeNode
当链表长度超过阈值,扩容到达64,则会转成红黑树,当节点删除小于阈值,则红黑树又会变为链表结构,TreeBin值为负数,-2
重要方法
构造器分析
可以看到实现了懒惰初始化,在构造方法中仅仅计算了 table 的大小,以后在第一次使用时才会真正创建
initialCapacity //初始容量,给map一个初始大小
loadFactor //负载因子,3/4,即扩容的阈值
concurrencyLevel //并发度
如果初始容量<并发度,则初始容量=并发度
创建大小,赋值给
get 流程
全程没有加锁
put 流程
以下哈希表简称(table),链表简称(bin)
如果为true,只有第一次放入键和值的时候才会有操作,如果为false,则每次都会覆盖旧值
size 计算流程
size 计算实际发生在 put,remove 改变集合元素的操作之中
没有竞争发生,向 baseCount 累加计数
有竞争发生,新建 counterCells,向其中的一个 cell 累加计数
counterCells 初始有两个 cell
如果计数竞争比较激烈,会创建新的 cell 来累加计数