12大原子更新类 - morris131/morris-book GitHub Wiki
- 基本类:AtomicInteger、AtomicLong、AtomicBoolean;
- 引用类型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference;
- 数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
- 属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
12大原子更新类的使用大同小异,下面以AtomicInteger为例。AtomicInteger的常用方法如下:
- int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
- boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
- int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
- int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
- int incrementAndGet():以原子方式将当前值加1后返回
package com.morris.atomic;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
private static AtomicInteger atomicInteger = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
final Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
final int num = i;
threads[i] = new Thread() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int now = atomicInteger.incrementAndGet();
System.out.println("我是线程:" + num + ",我得到值了,增加后的值为:" + now);
}
};
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("最终运行结果:" + atomicInteger.get());
}
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
Atomic包提供了3种基本类型的原子更新,但是Java的基本类型里还有char、float和double 等。那么如何原子的更新其他的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,让我们一起看一下Unsafe的源码。
Unsafe的源码:http://www.docjar.com/html/api/sun/misc/Unsafe.java.html
public final native boolean compareAndSwapObject(Object o,long offset,Objectexpected,Object x);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
参数解释:
- 参数1:对象所在的类本身的对象(一般这里是对一个对象的属性做修改,才会出现并发,所以该对象所存在的类也是有一个对象的)
- 参数2:这个属性在这个对象里面的相对便宜量位置,其实对比时是对比内存单元,所以需要属性的起始位置,而引用就是修改引用地址(根据OS、VM位数和参数配置决定宽度一般是4-8个字节),int就是修改相关的4个字节,而long就是修改相关的8个字节。 获取偏移量也是通过unsafe的一个方法:objectFieldOffset(Fieldfield)来获取属性在对象中的偏移量;静态变量需要通过:staticFieldOffset(Field field)获取,调用的总方法是:fieldOffset(Fieldfield)
- 参数3:修改的引用的原始值,用于对比原来的引用和要修改的目标是否一致。
- 参数4:修改的目标值,要将数据修改成什么。
对象的引用进行对比后交换,交换成功返回true,交换失败返回false,这个交换过程完全是原子的,在CPU上计算完结果后,都会对比内存的结果是否还是原先的值,若不是,则认为不能替换,因为变量是volatile类型所以最终写入的数据会被其他线程看到,所以一个线程修改成功后,其他线程就发现自己修改失败了。
通过代码,我们发现Unsafe只提供了3种CAS方法:compareAndSwapObject、compare- AndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现它是先把Boolean转换成整 型,再使用compareAndSwapInt进行CAS,所以原子更新char、float和double变量也可以用类似 的思路来实现。
package com.morris.atomic;
import java.util.concurrent.atomic.AtomicInteger;
public class ABATest {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
final int num = i;
new Thread() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicInteger.compareAndSet(0, 1)) {
System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
}
}
}.start();
}
new Thread() {
public void run() {
while (!atomicInteger.compareAndSet(1, 0))
;
System.out.println("已经改为原始值!");
}
}.start();
}
}
运行结果如下:
已经改为原始值!
我是线程:3,我获得了锁进行了对象修改!
我是线程:6,我获得了锁进行了对象修改!
可以发现,有两个线程修改了这个值,我们是想那一堆将0改成1的线程仅有一个成功。此时我们通过类来AtomicStampedReference解决这个问题。
package com.morris.atomic;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADealTest {
private static AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<Integer>(0, 1);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
final int num = i;
final int stamp = atomicInteger.getStamp();
new Thread() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicInteger.compareAndSet(0, 1, stamp, stamp + 1)) {
System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
}
}
}.start();
}
new Thread() {
public void run() {
int stamp = atomicInteger.getStamp();
while (!atomicInteger.compareAndSet(1, 0, stamp, stamp + 1)) {
}
System.out.println("已经改为原始值!");
}
}.start();
}
}