目录
AQS概述
- 操作系统层面:mutex+condition
- C++原子指令:CAS+LockSupport
- Java源码层面:CLH锁→AQS
- 应用层面:ReentrantLock,Semaphore,CountDownLatch,CyclicBarrier
操作系统层面
线程间通信方式
- 锁机制:互斥锁,条件变量,读写锁
- 互斥锁:提供了以排他方式阻止数据结构被并发修改的方法。
- 读写锁:允许多个线程同时读共享数据,而对写操作时互斥的。
- 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
- 信号量机制:包括无名线程信号量和命名线程信号量。
- 信号机制:类似进程间的信号处理
互斥锁与条件变量
- 互斥锁:失败后释放CPU
- 条件变量:一种相对复杂的线程同步方法,允许线程睡眠,直到满足某种条件,当满足条件时,可以向该线程发出信号,通知唤醒,条件变量是用来等待线程而不是上锁的
条件变量通常和互斥锁一起使用,互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足
两个线程利用条件变量及互斥锁实现同步:
- 一个线程利用条件变量实现等待,同时释放锁;
- 一个线程获取锁后利用该条件变量唤醒等待的线程
C++原子指令层面
CAS
一种乐观锁的实现方式,比较当前工作内存中的值和主内存中的值,如果相同则执行操作,否则继续重新判断,3个操作数:内存值V,旧的预期值A,要修改的更新值B
底层一般是是调用CompareAndSwapInt()方法,通常会和Volatile配合使用,保证可见和有序
LockSupport
一个线程阻塞工具类,所有的方法都是静态方法,在初始化的时候都是通过Unsafe去获得他们的内存地址,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit)。permit只有两个值1和0,默认是0。并且许可的累加上限是1。
permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park()方法会被唤醒,然后会将permit再次设置为0并返回。
Java层面
Java在jdk1.5版本之后的 java.util.concurrent包中引入了各类同步器,而其核心就是AQS,AQS的核心数据结构是一种名为CLH 锁的变体
CLH锁
传统的CLH锁是对自旋锁的一种改进:
- 将线程组织成一个队列,防止线程饥饿
- 锁状态去中心化,让每个线程在不同状态变量中自旋,减少cpu开销
CLH 锁是一种隐式的链表队列,没有显式的维护前驱或后继指针。因为每个等待获取锁的线程只需要轮询前一个节点的状态即可,而不需要遍历整个队列。在这种情况下,只需要使用一个局部变量保存前驱节点,而不需要显式的维护前驱或后继指针。
特点:
- 性能优异
- 公平锁,先入队先得到锁
- 实现简单,易于理解
- 扩展性强
缺点:
- 自旋,消耗cpu
- 需要改造,否则性能较为单一
AQS
AQS(AbstractQueuedSynchronizer)抽象队列同步器,即将暂时获取不到锁的线程加入到队列中,底层是基于CLH锁的变体,对CLH锁上述缺点进行改造:
- AQS 将自旋操作改为阻塞线程操作
- 改进主要包括三方面:
- 扩展每个节点的状态
- 显式的维护前驱节点和后继节点
- 诸如出队节点显式设为 null 等辅助 GC 的优化
应用层面
应用 | ReentrantLock | Semaphore | CountDownLatch | CyclicBarrier |
功能 | 轮询、超时、中断、公平和非公平锁等 | 用来控制同时访问特定资源的线程数量 | 允许 count 个线程阻塞在一个地方,直至所有线程的任务都执行完毕 | 让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门 |
实现原理 | state变量 | permits(可以大于1)+阻塞队列 | 设置初始state值为count | count 的初始值为 parties 属性的初始化值 |
涉及AQS方法 | tryAcquire() | CAS | tryReleaseShared()AQS判断state | ReentrantLock+Condition |