原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。
java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。
除了在i++操作时使用synchroinzed关键字实现同步外,还可以使用AtomicInteger原子类进行实现
-
.util.concurrent.atomic 包中提供了以下原子类, 它们是线程安全的类
- AtomicBoolean -- 原子布尔
- AtomicInteger -- 原子整型
- AtomicIntegerArray -- 原子整型数组
- AtomicLong -- 原子长整型
- AtomicLongArray -- 原子长整型数组
- AtomicReference -- 原子引用
- AtomicReferenceArray -- 原子引用数组
- AtomicMarkableReference -- 原子标记引用
- AtomicStampedReference -- 原子戳记引用
- AtomicIntegerFieldUpdater -- 用来包裹对整形 volatile 域的原子操作
- AtomicLongFieldUpdater -- 用来包裹对长整型 volatile 域的原子操作
- AtomicReferenceFieldUpdater -- 用来包裹对对象 volatile 域的原子操作
Java提供的原子类是靠 sun 基于 CAS 实现的,CAS 是一种乐观锁。参考:
原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。
AtomicInteger的实现
接下来通过源代码来看 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(Compare-And-Swap)算法保证数据操作的原子性。
CAS 算法是硬件对于并发操作共享数据的支持。
CAS 包含了三个操作数:
内存值 V 预估值 A 更新值 B当且仅当 V == A 时,V 将被赋值为 B,否则什么都不做,
当然如果需要的话,可以设计成自旋锁的模式,循环着不断进行判断 V 与 A 是否相等。
考虑如下问题:
关于CAS操作 提出问题:
情况1.两个线程A和B同时对AtomicInteger(10)进行incrementAndGet()方法,都获取到current = 10 ,compareAndSet比较时,内存总的值均未被修改, 那两个线程都将执行了+1,那返回的结果应该都为11吧?情况2两个线程A和B同时对AtomicInteger(10)进行incrementAndGet()方法,都获取到current = 10 ,线程A线程先进行了compareAndSwapInt导致内存中的值变为11,那线程B的在和内存中的值比较一直不相等, 那线程B不是死循环了吗?解决问题:
其实问题还是在CAS上,内存值,预估值,更新值的问题
情况1:不会存在返回结果都是 11 的情况。原子类提供的就是原子操作,多线程情况下不会存在数据不一致的情况。具体原因就是 CAS 操作,它会读取内存和预期值(11)作比较,如果相同才会进行赋值。情况2:同理。其实你说的两个线程“同时”,原子类的目的本身就是为了避免这种场景下的数据不一致,所以你说的这两种情况是不存在的。当然如果使用继承Thread类的方式实现多线程,那它的原子类变量是自己维护的,也就是线程独立的,那就会存在问题。实现Runnable接口就不会存在这个问题,因为是资源共享的。
参考:
参考: