AQS是并发基类 , 通过State
以及Exclussive Thread
来控制资源总数以及资源独占的线程. 通过LockSupport.park/unpark
来控制线程CPU的调度 , 用于让某个线程获取/让出CPU资源.
AQS主要分为两种模式 :
1
个资源 , 同一时刻只允许一个线程执行 BlockingQueue主要用于实现生产者-消费者模型的实例.
主要通过ReentrantLock
与Condition
来实现生产与消费. 其内部会创建两个ReentantLock :
notEmpty
的Condition , 用于阻塞take
函数获取数据notFull
的Condition , 用于阻塞put
函数插入数据内部也有capacity
变量用于标志当前队列总长度 , 也有AtomicInteger count
来标志当前队列长度.
当put
函数插入数据 , 而队列满了的时候 , 会通过notFull.await
让插入线程等待. 而当take
函数获取完数据 , 队列不满的时候 , 会通过notFull.signal
唤醒线程进行插入.
当take
函数获取数据, 而队列长度为0的时候 , 会通过notEmpty.await
等待数据插入. 而当put
函数调用后 , 队列中有数据时 , 会通过notEmpty.signal
唤醒线程获取数据.
CAS(Compare and Swap),即比较并交换。比较的是当前内存值与预期值,交换的是新值与内存中的值。这个操作是通过cmpxchg
指令来完成 , 但在该指令前 , 还有一个lock
前缀 , 而这个lock
前缀才是保证多核CPU的关键.
#define LOCK_IF_MP(mp) __asm cmp mp, 0 \
__asm je L0 \
__asm _emit 0xF0 \
__asm L0:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
// 如果是多核CPU , 则lock
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
而在上层 , Unsafe会通过自旋的方式一直等待cmpxchg
执行成功后返回结果.
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
volatile
关键字主要有两个功能 :
volatile
前的指令可以执行完毕在class
文件中 , 仅仅只是加上了ACC_VOLATILE
的Flag
在虚拟机中 , 会通过lock
前缀 , 来标志该条指令.
lock
指令用于在多处理器中执行指令时对共享内存的独占使用。它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。另外还提供了有序的指令无法越过这个内存屏障的作用。
在jdk1.5之前 , synchronized
关键字的性能非常差 , 因为在JVM中遇到synchronized
关键字就会使用Linux的Mutex
进行并发.
在jdk1.6版本后 , 通过对锁对优化 , synchronized
的性能已经达到一个很优秀的程度. 通过在JVM
加入了偏向锁
、轻量级锁
、重量级锁
的策略.
Java中锁膨胀的顺序为 : 无锁
, 偏向锁
, 轻量级锁
, 重量级锁
对象头
的Mark Word
中是否保存有线程ID , 如果有则认为当前锁处于偏向锁CAS
设置对象头中的线程ID , 如果成功 , 则代表从无锁成为偏向锁Mark Word
替换为指向 Lock Reocrd
的指针。Mark Word
是否指向当前线程的栈帧如果是则说明已经获取锁,否则说明其它线程竞争锁则膨胀为重量级锁。park
被挂起,退出同步块时unpark
唤醒后续线程。唤醒操作涉及到操作系统调度会有额外的开销。synchronized 关键字及 wait、notify、notifyAll 这三个方法都是通过MonitorEnter
、MonitorExit
来实现的.
等待队列处理策略
在JVM中 , 会通过Parker
来完成park/unpark
. 实际上 , 对应在Linux上的实现则也是通过mutex
来实现的:
pthread_cond_wait
以及 pthread_mutex_lock
实现等待pthread_cond_signal
以及pthread_mutex_lock
实现唤醒对于当代多核CPU而言 , CPU会有缓存一致性协议 (MESI) , 主要用于标示CPU Cache Line中缓存的状态 , 并且用于同步多个CPU中缓存状态 .
而在CPU指令中Lock
指令用来同步各个CPU核之间的缓存数据.
乐观锁 : 当并发量级很小, 冲突小时 , 使用乐观锁 , 比如volatile
、CAS
操作
悲观锁 : 当并发量级比较大, 冲突较大时 , 使用悲观锁 , 比如ReentrantLock
、synchronized
, 主要因为CAS自旋多次后 , 会导致CPU大量的空旋 , 浪费cpu资源.