CAS(Compare And Swap)
CAS可以简单的理解为比较并替换,利用CPU的CAS指令,同时借助JNI(Java Native Interface为JAVA本地调用)来完成Java的非阻塞算法,实现原子操作。
CAS机制中,使用了3个参数,当前状态下内存中的数值:a;内存中原值:b(该值不是当前状态下内存中的值,而实在操作开始时,从内存中读取的值);要修改的新值:c。在更新一个变量的时候,当且仅当a与b的值相等的时候,才会将内存中的值(也就时当前内存中的值a)更新为c。如果a与b的值不相等,则重新读取内存中的值,进行相应的数据操作之后,再重复这样的过程来对值进行更新。即:通过自旋来进行更新操作。
CAS的缺点以及相应的优化
- ABA问题。CAS通过判断原值与当前值是否一致,即,内存中的值没有过变化,从而进行更新操作。如果有内存中的初始值为A,另外一个线程将值修改为B,再修改为A,再做比较时,并未发现内存值的变化,但是,实际上值是有变化的。在JDK1.5之后提供了AtomicStampedReference类来解决ABA问题,保存元素的引用,引用相当于版本号,是每一个变量的标识,因此在CAS前判断下是否是同一个引用即可
- 自旋时间长,CPU开销大。
- 只能保证一个共享变量的原子操作,当有多个共享变量需要原子操作时,CAS就无法保证了。可以将多个变量合并成一个共享变量(JAVA的组合),JDK1.5以后提供了AtomicReference类,来保证引用对象之间的原子性,可以将多个变量放到一个对象中,进行原子操作。
AQS(AbustactQueuedSynchronizer)
AbustactQueuedSynchronizer维护了一个volatile int类型的变量state,用于表示当前同步状态(volatile虽然不能保证操作的原子性,但是保证了当前变量state的可见性,即:值的改变会立刻同步到主内存中,对于其余线程可见)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。
源码中,对于state的操作如下:
/**
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
其中 unsafe是 java通过调用本地方法,提供了硬件级别的原子操作。
AQS定义两种资源共享方式:Exclusive(独占式),如:ReentrantLock;Share(共享式),如:CountDownLatch。具体的同步类,只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经实现好了。具体的同步类只需要实现以下几个方法即可:
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
- tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
CAS和AQS为java并发提供了坚实的基础,后面结合具体的同步类ReentrantLock和CountDownLatch的源码,再来更好的理解这两个原理