原子操作类包括以下几类:
-
基本类:AtomicInteger、AtomicLong、AtomicBoolean。
-
引用类型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference。
-
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。
-
属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。
原子操作类的使用
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.concurrent.thread.atomic;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
public static AtomicInteger inc = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
inc.incrementAndGet();
///System.out.println(inc.get());
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await(); // 保证前面的线程都执行完
System.out.println(inc.get()); // 10000
}
}
AtomicReference的使用
package com.morris.concurrent.atomic.primary;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceDemo {
public static void main(String[] args) {
Person p1 = new Person("bob");
Person p2= new Person("morris");
AtomicReference<Person> atomicReference = new AtomicReference(p1);
atomicReference.compareAndSet(p1, p2);
System.out.println(atomicReference.get().name);
// morris
}
private static class Person {
String name;
Person(String name) {
this.name = name;
}
}
}
AtomicIntegerArray的使用
package com.morris.concurrent.atomic;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayDemo {
public static void main(String[] args) {
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
atomicIntegerArray.set(1, 10);
atomicIntegerArray.compareAndSet(1, 10, 20);
System.out.println(atomicIntegerArray.get(1)); // 20
}
}
AtomicIntegerFieldUpdater的使用
package com.morris.concurrent.atomic;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterDemo {
public static void main(String[] args) {
Person person = new Person();
AtomicIntegerFieldUpdater atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
atomicIntegerFieldUpdater.set(person, 18);
atomicIntegerFieldUpdater.compareAndSet(person, 18, 20);
System.out.println(atomicIntegerFieldUpdater.get(person));
// 20
}
private static class Person {
volatile int age; // 注意age必须用volatile修饰
}
}
ABA问题
ABA问题的发生
ABA:假设有另外一个线程将值原来是A,先修改成B,再修改回成A,当前线程的CAS操作无法分辨当前V值是否发生过变化。
package com.morris.concurrent.thread.atomic;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ABADemo {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
int expect = 0;
int update = 1;
if (atomicInteger.compareAndSet(expect, update)) {
System.out.printf("%s将值[%d]修改为[%d]\n", Thread.currentThread().getName(), expect, update);
}
}).start();
}
new Thread(() -> {
int expect = 1;
int update = 0;
while (!atomicInteger.compareAndSet(expect, update)) {
}
System.out.printf("%s将值[%d]修改为[%d]\n", Thread.currentThread().getName(), expect, update);
}).start();
}
}
运行结果如下:
Thread-0将值[0]修改为[1]
Thread-10将值[1]修改为[0]
Thread-3将值[0]修改为[1]
可以发现,有两个线程修改了这个值,我们是想那一堆将0改成1的线程仅有一个成功。此时我们通过类来AtomicStampedReference或AtomicMarkableReference解决这个问题。
使用AtomicStampedReference解决ABA问题
AtomicStampedReference使用了版本号来解决ABA问题,值的每次修改都会更新版本号,每次比较时不仅会比较值,还会比较版本号。
package com.morris.concurrent.atomic;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABAAtomicStampedReference {
// 第一个参数为初始值0
// 第二个参数为初始版本为0
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(0, 0);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
int expect = 0;
int update = 1;
int stamp = 0;
int newStamp = 1;
if (atomicStampedReference.compareAndSet(expect, update, stamp, newStamp)) {
System.out.printf("%s将值[%d]修改为[%d],版本[%d]修改为[%d]\n", Thread.currentThread().getName(), expect, update, stamp, newStamp);
}
}).start();
}
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
int expect = 1;
int update = 0;
int stamp = 1;
int newStamp = 2;
if (atomicStampedReference.compareAndSet(expect, update, stamp, newStamp)) {
System.out.printf("%s将值[%d]修改为[%d],版本[%d]修改为[%d]\n", Thread.currentThread().getName(), expect, update, stamp, newStamp);
}
}).start();
}
}
使用AtomicMarkableReference解决ABA问题
AtomicMarkableReference使用标记来解决ABA问题,值的每次修改都会更新标记,每次比较时不仅会比较值,还会比较标记。
AtomicStampedReference关心的是值被改了多少次。而AtomicMarkableReference关心的值有没有被改变。
package com.morris.concurrent.atomic;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
public class ABAAtomicMarkableReference {
// 第一个参数为初始值0
// 第二个参数为初始标记为0
private static AtomicMarkableReference<Integer> atomicStampedReference = new AtomicMarkableReference(0, true);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
int expect = 0;
int update = 1;
boolean expectMark = true;
boolean newMark = false;
if (atomicStampedReference.compareAndSet(expect, update, expectMark, newMark)) {
System.out.printf("%s将值[%d]修改为[%d],标记[%s]修改为[%s]\n", Thread.currentThread().getName(), expect, update, expectMark, newMark);
}
}).start();
}
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
int expect = 1;
int update = 0;
boolean expectMark = false;
boolean newMark = true;
if (atomicStampedReference.compareAndSet(expect, update, expectMark, newMark)) {
System.out.printf("%s将值[%d]修改为[%d],标记[%s]修改为[%s]\n", Thread.currentThread().getName(), expect, update, expectMark, newMark);
}
}).start();
}
}
实现原理
Atomic包里的类基本都是使用Unsafe实现的,让我们一起看一下Unsafe的源码。
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, 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类型所以最终写入的数据会被其他线程看到,所以一个线程修改成功后,其他线程就发现自己修改失败了。
Atomic包提供了3种基本类型的原子更新,但是Java的基本类型里还有char、float和double等。那么如何原子的更新其他的基本类型呢?
通过代码,我们发现Unsafe只提供了3种CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现它是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新char、float和double变量也可以用类似的思路来实现。