文章目录
- 原子类型
- AtomicInteger 原子整数
- AtomicReferenc 原子引用
- AtomicStampedReference 带版本号的原子引用
- AtomicMarkableReference 仅记录是否修改的原子引用
- AtomicXXXArray 原子数组
- AtomicXXXFieldUpdater 字段更新器
- LongAdder累加器
原子类型
AtomicInteger 原子整数
public class test6 {
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger();
System.out.println(i.getAndIncrement()); // i++ sout: 0
System.out.println(i.incrementAndGet()); // ++i sout: 2
System.out.println(i.decrementAndGet()); // --i sout: 1
System.out.println(i.getAndDecrement()); // i-- sout: 1
// i = 0
System.out.println(i.getAndAdd(5)); // sout:0 i += 5
System.out.println(i.addAndGet(-5)); // i -= 5 sout:0
// i = 0
System.out.println(i.getAndUpdate(p -> p - 2)); // sout:0 p -= 2
System.out.println(i.updateAndGet(p -> p + 2)); // p += 2 sout: 0
}
}
AtomicReferenc 原子引用
class DecimalAccountSafeCas{
AtomicReference<BigDecimal> ref;
public DecimalAccountSafeCas(BigDecimal balance) {
ref = new AtomicReference<>(balance);
}
public BigDecimal getBalance() {
return ref.get();
}
public void withdraw(BigDecimal amount) {
while (true) {
BigDecimal prev = ref.get();
BigDecimal next = prev.subtract(amount); // 减小
if (ref.compareAndSet(prev, next)) { // cas
break;
}
}
}
}
AtomicStampedReference 带版本号的原子引用
解决aba问题,aba问题之前介绍过了。
如果版本号与之前获取的相同,那么就进行交换,同时更新版本号。
可以知道更改次数。
class DecimalAccountSafeCas{
AtomicStampedReference<BigDecimal> ref;
public DecimalAccountSafeCas(BigDecimal balance) {
ref = new AtomicStampedReference<>(balance, 0); // 初始版本号0
}
public BigDecimal getBalance() {
return ref.getReference();
}
public void withdraw(BigDecimal amount) {
while (true) {
BigDecimal prev = ref.getReference();
int stamp = ref.getStamp();// 获取版本号
// 如果这之中有其他线程又修改了ref,那么版本号改变与之前不同。就会cas失败
BigDecimal next = prev.subtract(amount); // 减小
if (ref.compareAndSet(prev, next, stamp, stamp + 1)) { // cas。版本号相同才交换,并且更新版本号
break;
}
}
}
}
AtomicMarkableReference 仅记录是否修改的原子引用
我们一般不关心中间修改了多少次,我么只在乎是否修改过,那么就可以用这个类。
所以一个boolean值就可以记录。
class DecimalAccountSafeCas{
AtomicMarkableReference<BigDecimal> ref;
public DecimalAccountSafeCas(BigDecimal balance) {
ref = new AtomicMarkableReference<>(balance, true);
}
public BigDecimal getBalance() {
return ref.getReference();
}
public void withdraw(BigDecimal amount) {
while (true) {
BigDecimal prev = ref.getReference();
BigDecimal next = prev.subtract(amount); // 减小
// 如果这时有线程修改,把mark变为了false,那么就会cas就会失败
// 因为只有true,和false,我们就直接就对比是否是true就行了,不用在获取
if (ref.compareAndSet(prev, next, true, false)) { // cas。版本号相同才交换,并且更新版本号
break;
}
}
}
}
AtomicXXXArray 原子数组
我么一般都不是修改引用指向,而是引用里的内容,就比如数组。
下面是线程安全的数组。
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
这里我么先用普通数组,来看看是否有线程安全问题:
package com.leran;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class test7 {
public static void main(String[] args) {
demo(
() -> new int[10],
(array) -> array.length,
(array, idx) -> array[idx]++,
array -> System.out.println(Arrays.toString(array))
);
}
private static <T> void demo(
Supplier<T> arraySupplier, // 生产者,无中生有
Function<T, Integer> lengthFun, // 一个参数一个结果 BiFunction 多个参数,多个结果
BiConsumer<T, Integer> putConsumer, // 消费者多个参数,无结果
Consumer<T> printConsumer ) { // 一个参数,无结果
List<Thread> ts = new ArrayList<>();
T array = arraySupplier.get(); // 获取传入的数组
int length = lengthFun.apply(array); // 获取长array长度
for (int i = 0; i < length; i++) {
// 每个线程对数组作 10000 次操作
ts.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
putConsumer.accept(array, j%length);
}
}));
}
ts.forEach(t -> t.start()); // 启动所有线程
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}); // 等所有线程执行完,输出
printConsumer.accept(array);
}
}
显然是不安全的,下面介绍安全的AtomicIntegerArray。
demo方法不变,改变传入的数组。
demo(
() -> new AtomicIntegerArray(10),
(array) -> array.length(),
(array, idx) -> array.getAndIncrement(idx),
array -> System.out.println(array)
);
其他用法相似。
AtomicXXXFieldUpdater 字段更新器
用于更新某一类中的属性。
- AtomicReferenceFieldUpdater // 域字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
必须配合volatile使用。
如:AtomicIntegerFieldUpdater
public class test8 {
public static void main(String[] args) {
AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age");
Student student = new Student();
fieldUpdater.compareAndSet(student, 0, 5);
System.out.println(student.age); // 5
fieldUpdater.compareAndSet(student,5, 10);
System.out.println(student.age); // 10
fieldUpdater.compareAndSet(student, 5, 20); // 修改失败
System.out.println(student.age); // 10
}
}
class Student{
volatile int age;
@Override
public String toString() {
return "student{" +
"age=" + age +
'}';
}
}
其他用法相似。
LongAdder累加器
java中专门用于累加的,所以性能肯定比我么AtomicLong好。
下面是对比。
public class test9 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
}
for (int i = 0; i < 5; i++) {
demo(() -> new LongAdder(), adder -> adder.increment());
}
}
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
long start = System.nanoTime();
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 40; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(adder);
}
}));
}
ts.forEach(t -> t.start());
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(adder + " cost:" + (end - start)/1000_000 + "ns");
}
}
前五次是AtomicLong累加
后五次是LongAdder累加
20000000 cost:414ns
20000000 cost:390ns
20000000 cost:366ns
20000000 cost:377ns
20000000 cost:414ns
20000000 cost:33ns
20000000 cost:26ns
20000000 cost:26ns
20000000 cost:27ns
20000000 cost:37ns
Process finished with exit code 0
显然是快了10倍左右。
性能提升的原因,就是在有竞争时,设置多个累加单元Cell,最后将结果汇总。
这样在累加时操作的是不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。