Java提供的原子类是靠 sun 基于 CAS 实现的,CAS 是一种乐观锁。
原子变量类相当于一种泛化的 volatile
变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger
表示一个int
类型的值,并提供了 get
和 set
方法,这些 Volatile
类型的int
变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet
方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile
变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger
表面上非常像一个扩展的 Counter
类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。
AtomicInteger的实现
AtomicInteger
是一个支持原子操作的 Integer
类,就是保证对 AtomicInteger
类型变量的增加和减少操作是原子性的,不会出现多个线程下的数据不一致问题。如果不使用 AtomicInteger
,要实现一个按顺序获取的 ID,就必须在每次获取时进行加锁操作,以避免出现并发时获取到同样的 ID 的现象。
接下来通过源代码来看 AtomicInteger 具体是如何实现的原子操作。
首先看 value
的声明:
private volatile int value;
volatile
修饰的 value
变量,保证了变量的可见性。
incrementAndGet()
方法,下面是具体的代码:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
通过源码,可以知道,这个方法的做法为先获取到当前的 value
属性值,然后将 value
加 1,赋值给一个局部的 next
变量,然而,这两步都是非线程安全的,但是内部有一个死循环,不断去做 compareAndSet
操作,直到成功为止,也就是修改的根本在 compareAndSet
方法里面,compareAndSet()
方法的代码如下:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
compareAndSet()
方法调用的compareAndSwapInt()
方法的声明如下,是一个native
方法。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, intvar5);
compareAndSet
传入的为执行方法时获取到的 value
属性值,next
为加 1 后的值, compareAndSet
所做的为调用 Sun 的 UnSafe
的 compareAndSwapInt
方法来完成,此方法为 native
方法,compareAndSwapInt 基于的是 CPU 的 CAS 指令来实现的。所以基于 CAS 的操作可认为是无阻塞的,一个线程的失败或挂起不会引起其它线程也失败或挂起。并且由于 CAS 操作是 CPU 原语,所以性能比较好。
类似的,还有 decrementAndGet()
方法。它和 incrementAndGet()
的区别是将 value
减 1,赋值给next
变量。
AtomicInteger
中还有 getAndIncrement()
和 getAndDecrement()
方法,他们的实现原理和上面的两个方法完全相同,区别是返回值不同,前两个方法返回的是改变之后的值,即 next
。而这两个方法返回的是改变之前的值,即 current
。还有很多的其他方法,就不列举了。
CAS算法
CAS(Compare-And-Swap)算法保证数据操作的原子性。
CAS 算法是硬件对于并发操作共享数据的支持。
CAS 包含了三个操作数:
- 内存值 V
- 预估值 A
- 更新值 B
当且仅当 V == A 时,V 将被赋值为 B,否则循环着不断进行判断 V 与 A 是否相等。
关于乐观锁与悲观锁
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
CAS便是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
比如说synchronized就是一种独占锁,他假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
缺点:
由于在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,它不能做任何事。举个栗子,如果一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价就会非常的高。