文章目录
一、原子整数
JUC并发包提供了:
- AtomicBoolean
- AtomicInteger
- AtomicLong
下面以AtomicInteger为例,来了解下常用API,所有的操作都是原子的。
- 自增:
- incrementAndGet():自增并获取,等同于++i
- getAndIncrement():获取并自增,等同于i++
- 自减
- decrementAndGet():自减并获取,等同于–i
- getAndDecrement():获取并自减,等同于i–
- 加减法
- getAndAdd(int delta):获取并加delta
- addAndGet(int delta):加delta并获取
可以利用上面的API对我们之前的取款案例(传送门)进一步改造,如下:
// 只更改withdraw()方法
// 之前withdraw()
@Override
public void withdraw(Integer amount) {
while (true) {
int prev = balance.get();
int next = prev - amount;
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
// 改造后的withdraw
@Override
public void withdraw(Integer amount) {
balance.getAndAdd(amount);
}
很显然,改造后能实现相同的功能,但是要更简洁。
-
复杂运算
-
updateAndGet(IntUnaryOperator updateFunction):更新并获取
-
getAndUpdate(IntUnaryOperator updateFunction):后去并更新
IntUnaryOperator updateFunction :这是什么参数呢?
@FunctionalInterface public interface IntUnaryOperator { /** * Applies this operator to the given operand. * * @param operand the operand * @return the operator result */ int applyAsInt(int operand); //... }
通过查看源码发现,这是一个函数式接口,示例,我们做一个简单的乘法运算:
import java.util.concurrent.atomic.AtomicInteger; public class TestAtmoticInteger { public static void main(String[] args) { AtomicInteger ai = new AtomicInteger(10); ai.updateAndGet(n -> n * 10); System.out.println(ai.get()); } } // 测试结果 100
用起来是不是很简单,那么是怎么实现的呢?下面简单介绍下原理,我们通过while循环+compareAndSet来模拟updateAndGet来实现乘法运行,代码如下:
import java.util.concurrent.atomic.AtomicInteger; public class TestAtmoticInteger { public static void main(String[] args) { AtomicInteger ai = new AtomicInteger(10); // ai.updateAndGet(n -> n * 10); // System.out.println(ai.get()); while (true) { int prev = ai.get(); int next = prev * 10; if (ai.compareAndSet(prev, next)) { break; } } System.out.println(ai.get());
上面同样可以实现相同的效果,我们来看下源码是如何实现的
/** * Atomically updates the current value with the results of * applying the given function, returning the updated value. The * function should be side-effect-free, since it may be re-applied * when attempted updates fail due to contention among threads. * * @param updateFunction a side-effect-free function * @return the updated value * @since 1.8 */ public final int updateAndGet(IntUnaryOperator updateFunction) { int prev, next; do { prev = get(); next = updateFunction.applyAsInt(prev); } while (!compareAndSet(prev, next)); return next; }
源码也是这么实现的。
-
二、原子引用
1、AtomicReference-原子引用类型
为什么需要有原子引用呢?有时候我们修改不是仅仅需要修改变量的值,还需要修改引用类型的成员变量值,原子整数这里就不适用了,所以就有了原子引用。
以之前的取款案例,我们都知道实际生活呢,取款金额可以是小数的,涉及金额的小说我们一般使用BigDecimal处理,代码如下:
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 账户接口
*/
public interface AccountDecimal {
// 获取余额
BigDecimal getBalance();
// 取款
void withdraw(BigDecimal amount);
// 模拟多个线程取款操作
static void multiWithdraw(AccountDecimal account) {
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(BigDecimal.TEN);
}));
}
long start = System.currentTimeMillis();
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.currentTimeMillis();
System.out.println("balance: "+ account.getBalance() + " cost: " + (end - start) + " ms");
}
}
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* CAS无锁实现
*/
public class AccountDecimalCAS implements AccountDecimal {
private final AtomicReference<BigDecimal> balance;
public AccountDecimalCAS(BigDecimal balance) {
this.balance = new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
@Override
public void withdraw(BigDecimal amount) {
balance.getAndUpdate(m -> m.subtract(amount));
}
}
import java.math.BigDecimal;
/**
* 测试类
*/
public class TestAccount {
public static void main(String[] args) {
AccountDecimal account3 = new AccountDecimalCAS(new BigDecimal("10000"));
AccountDecimal.multiWithdraw(account3);
}
}
// 测试结果
balance: 0 cost: 67 ms
- 说明:我们这里使用1000个线程并不符合CAS使用场景,但是这里我们只是为了测试这么做,实际使用中并不适合。
2、AtomicStampedReference-原子引用类型(版本标记)
原子引用中的ABA问题,即初始值为A改为B之后再改会A,我们是否能判断改变量是否被修改过呢?
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j(topic = "c.TestABA")
public class TestABA {
static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
log.debug("main start ...");
// 获取值A
String prev = ref.get();
other();
TimeUnit.SECONDS.sleep(1);
// 尝试更改为C
log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
}
private static void other() {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
}, "t1").start();
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
}, "t2").start();
}
}
// 测试结果
2021-09-27 10:44:52.619 DEBUG [main] c.TestABA - main start ...
2021-09-27 10:44:52.662 DEBUG [t1] c.TestABA - change A->B true
2021-09-27 10:44:53.161 DEBUG [t2] c.TestABA - change B->A true
2021-09-27 10:44:54.161 DEBUG [main] c.TestABA - change A->C true
有A修改为C成功了,但是我们并不能判定A是否被修改过(其他已经修改过了)。当然大部分情况下ABA问题不影响业务,也不排除会有影响的情况。当有需要这种场景-一旦共享变量被修改,其他线程可以感知到,我们改怎么做呢?需要用到AtomicStampedReference类,还是通过上面小案例,来了解AtomicStampedReference使用。
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
@Slf4j(topic = "c.TestABA")
public class TestABAStamped {
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException {
log.debug("main start ...");
// 获取值A
String prev = ref.getReference();
int stamp = ref.getStamp();
other();
TimeUnit.SECONDS.sleep(1);
// 尝试更改为C
log.debug("change A->C {},stamp {}->{}", ref.compareAndSet(prev, "C", stamp, stamp + 1), stamp, stamp + 1);
}
private static void other() {
new Thread(() -> {
int stamp = ref.getStamp();
log.debug("change A->B {},stamp {}->{}", ref.compareAndSet(ref.getReference(), "B", stamp, stamp + 1), stamp, stamp + 1);
}, "t2").start();
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
int stamp = ref.getStamp();
log.debug("change B->A {},stamp {}->{}", ref.compareAndSet(ref.getReference(), "A", stamp, stamp + 1), stamp, stamp + 1);
}, "t2").start();
}
}
// 测试结果
2021-09-27 10:57:45.997 DEBUG [main] c.TestABA - main start ...
2021-09-27 10:57:46.039 DEBUG [t2] c.TestABA - change A->B true,stamp 0->1
2021-09-27 10:57:46.539 DEBUG [t2] c.TestABA - change B->A true,stamp 1->2
2021-09-27 10:57:47.539 DEBUG [main] c.TestABA - change A->C false,stamp 0->1
当回到主线程时,当前版本还是0,而其他线程已经更改版本为2,虽然值都是A,因为版本不一致,导致最后修改失败,这就解决了ABA问题。
3、AtomicMarkableReference-原子引用类型(布尔型标记)
AtomicStampedReference可以给原子引用加上版本号,追踪原子引用整个的变化过程;但是有时候,我们并不关心引用变量变更了多少次,只是单纯的关心是否变更过,这里就使用AtomicMarkableReference类。
- AtomicStampedReference内部使用整数记录版本号
- AtomicMarkableReference:内部使用布尔值来记录是否变更过
场景描述:日常家里有垃圾我们都会扔垃圾桶中的垃圾袋中,如果垃圾袋满了,我们需要更换一个新的空的垃圾袋;假设现在垃圾袋满了,我们需要更换新的垃圾袋,怎么实现呢?
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 垃圾袋
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GarbageBag {
private String desc;
}
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
/**
* 更换垃圾袋
*/
@Slf4j(topic = "c.ReplaceGarbageBag")
public class ReplaceGarbageBag {
public static void main(String[] args) throws InterruptedException {
GarbageBag bag = new GarbageBag("满的垃圾袋");
// true表示垃圾袋已满,false为空
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
log.debug("start ...");
GarbageBag prev = ref.getReference();
log.debug(prev.toString());
TimeUnit.SECONDS.sleep(1);
log.debug("换一个空的垃圾袋?");
boolean success = ref.compareAndSet(prev, new GarbageBag("空的垃圾袋"), true, false);
log.debug("更换了吗?{}", success);
log.debug(ref.getReference().toString());
}
}
// 测试结果
2021-09-27 11:15:49.322 DEBUG [main] c.ReplaceGarbageBag - start ...
2021-09-27 11:15:49.324 DEBUG [main] c.ReplaceGarbageBag - GarbageBag(desc=满的垃圾袋)
2021-09-27 11:15:50.325 DEBUG [main] c.ReplaceGarbageBag - 换一个空的垃圾袋?
2021-09-27 11:15:50.325 DEBUG [main] c.ReplaceGarbageBag - 更换了吗?true
2021-09-27 11:15:50.326 DEBUG [main] c.ReplaceGarbageBag - GarbageBag(desc=空的垃圾袋)
三、原子数组
有时候我们需要包含数组内元素的线程安全性,这时候我们需要用到原子数组相关的类。
- AtomicIntegerArray:原子整形数组
- AtomicLongArray:原子长整形数组
- AtomicReferenceArray:原子引用数组
下面我们通过代码来测试下普通数组和原子数组的线程安全性
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class TestArray {
public static void main(String[] args) {
// 普通数组
demo(
() -> new int[10],
array ->array.length,
(array, index) -> array[index]++,
array -> System.out.println(Arrays.toString(array))
);
// 原子数组
demo(
() -> new AtomicIntegerArray(10),
AtomicIntegerArray::length,
AtomicIntegerArray::getAndIncrement,
System.out::println
);
}
/**
参数1,提供数组、可以是线程不安全数组或线程安全数组
参数2,获取数组长度的方法
参数3,自增方法,回传 array, index
参数4,打印数组的方法
*/
// supplier 提供者 无中生有 ()->结果
// function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
// consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->
private static <T> void demo(
Supplier<T> arraySupplier,
Function<T, Integer> lengthFun,
BiConsumer<T, Integer> putConsumer,
Consumer<T> printConsumer
) {
List<Thread> list = new ArrayList<>();
T array = arraySupplier.get();
int len = lengthFun.apply(array);
for (int i = 0; i < len; i++) {
list.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
putConsumer.accept(array, j % len);
}
}));
}
list.forEach(Thread::start);
list.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
printConsumer.accept(array);
}
}
// 测试结果
[8910, 8911, 8901, 8907, 8871, 8893, 8879, 8876, 8883, 8882]
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
说明:代码中几个都为JDK8新特性中函数式接口,lambda表达式的内容。有兴趣的或者不懂的可自行查阅相关文档,这里不在详述。
四、原子更新器
有以下三种原子更新器:
- AtomicReferenceFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
利用原子更新器,可以针对对象的某个域(Field字段)进行原子操作,只能配合volatile修饰的字段使用,否则报错:Exception in thread “main” java.lang.IllegalArgumentException: Must be volatile type
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
@Slf4j(topic = "c.TestAtomicFieldUpdater")
public class TestAtomicFieldUpdater {
public static void main(String[] args) {
Student stu = new Student();
AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
System.out.println(updater.compareAndSet(stu, null, "张三"));
System.out.println(updater.get(stu));
}
}
class Student {
volatile String name;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
QQ:806797785
仓库地址:https://gitee.com/gaogzhen/concurrent