原子类
目录
1. 原子类分类
原子类基本都在java.util.concurrent.atomic 包中
1.1 原子基本类型
- AtomicBoolean
- AtomicInteger
- AtomicLong
1.2 原子数组类型
- AtomicIntegerArray
- AtomicLongArray
1.3 原子引用类型
- AtomicReference
- AtomicStampedReference
1.4 原子属性类型
- AtomicLongFieldUpdater
- AtomicIntegerFieldUpdater
2. 非原子类实例
测试代码
public class Test {
static Long totalCount = 0L;
public static void main(String[] args) {
int j = 0;
while (j < 100) {
totalCount = 0L;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
totalCount++;
}
}
}, "线程1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
totalCount++;
}
}
}, "线程1");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
System.out.println("当前总数量为:" + totalCount);
} catch (InterruptedException e) {
e.printStackTrace();
}
j++;
}
}
}
测试结果
当前总数量为:884
当前总数量为:717
当前总数量为:563
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:686
当前总数量为:1000
...
结论 :可以看出非原子类在多线程的时候,获得的结果并不是我们想要的
3. 原子类实例
测试代码
public class Test {
// static Long totalCount = 0L;
static AtomicInteger totalCount;
public static void main(String[] args) {
int j = 0;
while (j < 100) {
//原子类,初始值为0
totalCount = new AtomicInteger(0);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
//totalCount++;
totalCount.getAndIncrement();
}
}
}, "线程1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
//totalCount++;
totalCount.getAndIncrement();
}
}
}, "线程1");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
// System.out.println("当前总数量为:" + totalCount);
System.out.println("当前总数量为:" + totalCount.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
j++;
}
}
}
测试结果
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
结论 : AtomicInteger 可以保证在多线程的情况下数据的原子性
4. AtomicInteger 常用方法
- getAndIncrement 先使用再自增1,相当于 n++
- getAndDecrement 先使用,再自减1,相当于 n–
- decrementAndGet 先减1,再使用,相当于–n
- incrementAndGet 先加1,在使用。相当于 ++n
- get 获取当前的值
4. AtomicInteger 源码分析
使用的是unsafe 方法的getAndAddInt 来进行自增的
this: 为当前的值
valueOffset 地址偏移量,助你找到n在堆中的地址
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
getIntVolatile 通过volatile直接获取内存中获取到预期值 :var5
var1:当前值
var2 :地址偏移量
var5:预期值compareAndSwapInt 进行CAS操作
var1:当前值
var2 :地址偏移量
var4 :递增间隔
var5:预期值
比较 var1 和 var5 是否相同,
相同,代表没有线程更改过,,当前值(var1) = 预期值(var5) + 递增间隔 (var4) 返回true
不相同,代表线程更改过,,当前值(var1) = 预期值(var5) 返回false,再重试
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
5. ABA问题
引发原因
- 线程1 将var1 =A 读取到线程内存中(栈),
- 线程2 将var1 改成B ,又改成成A
- 线程1 在获取预期值var5时, 发现还是A, 符合预期,导致没有返现被更改
- 其实线程2已经做过更改
6. ABA解决
解决思路,对变量增加时间戳或版本号,这样的话,改了的话,时间戳或版本号会更改,在CAS的时候能被发现
AtomicStampedReference 解决ABA问题
getReference 获取预期值
getStamp 获取时间戳
测试代码
public class ABATest {
private static AtomicStampedReference<Integer> totalCount;
public static void main(String[] args) {
int j = 0;
while (j < 100) {
//原子类,初始值为0
totalCount = new AtomicStampedReference<>(0, 0);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
//totalCount++;
Integer reference;
int stamp;
do {
reference = totalCount.getReference();
stamp = totalCount.getStamp();
} while (!totalCount.compareAndSet(reference, reference + 1, stamp, stamp + 1));
}
}
}, "线程1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
Integer reference;
int stamp;
do {
reference = totalCount.getReference();
stamp = totalCount.getStamp();
} while (!totalCount.compareAndSet(reference, reference + 1, stamp, stamp + 1));
}
}
}, "线程1");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
// System.out.println("当前总数量为:" + totalCount);
System.out.println("当前总数量为:" + totalCount.getReference());
} catch (InterruptedException e) {
e.printStackTrace();
}
j++;
}
}
}
测试结果
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000
当前总数量为:1000