# 1.常见的JUC整数并发包
- AtomicBoolean
- AtomicInteger
- AtomicLong
# 2.AtominInteger常用方法
AtomicInteger i = new AtomicInteger(0);
// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
System.out.println(i.getAndIncrement());
// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
System.out.println(i.incrementAndGet());
// 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
System.out.println(i.decrementAndGet());
// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
System.out.println(i.getAndDecrement());
// 获取并加值(i = 0, 结果 i = 5, 返回 0)
System.out.println(i.getAndAdd(5));
// 加值并获取(i = 5, 结果 i = 0, 返回 0)
System.out.println(i.addAndGet(-5));
// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.getAndUpdate(p -> p - 2));
// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.updateAndGet(p -> p + 2));
// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));
}
# 2.1 updateAndGet()方法原理
- updateAndGet源码
/**
* 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;
}
IntUnaryOperator 是一个函数式接口。可以通过lambda表达式进行传参数,例如p -> p + 2
get()方法就是获取当前内存中的值赋给prev,利用传递进来的lambda表达式,pre就是p,然后执行p+2的操作赋给next,next就是我们修改过后的值。进入while循环,通过compareAndSet方法,如果我们之前获取的值prev和当前内存中的值相等,就代表这段时间内,没有其他线程修改过这个值,然后就将内存值存储的值修改为next。并且返回。如果我们之前获取的值prev和当前内存中的值不相等,就重新进行尝试。
# 3.原子引用
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference 当我们想保护的类型不仅仅是基本类型,例如我们想保护BigDecimal,这种小数类型。可以采用以下这种方法。
# 3.1举例
假如同时一个账户余额为10000,现在有一千个线程对其进行减10元操作。
public class StrDouTest {
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000")));
}
}
interface DecimalAccount {
// 获取余额
BigDecimal getBalance();
// 取款
void withdraw(BigDecimal amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(DecimalAccount account) {
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(BigDecimal.TEN);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(account.getBalance());
}
}
class DecimalAccountSafeCas implements DecimalAccount {
AtomicReference<BigDecimal> ref;
public DecimalAccountSafeCas(BigDecimal balance) {
ref = new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return ref.get();
}
@Override
public void withdraw(BigDecimal amount) {
while (true) {
BigDecimal prev = ref.get();
BigDecimal next = prev.subtract(amount);
if (ref.compareAndSet(prev, next)) {
break;
}
}
}
}
我们可以在AtomicReference中指定我们要保护的类型(AtomicReference)。然后通过原子引用中的一些方法进行相应的原子操作。
# 4.原子数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray 当有时候我们想保护的类型既不是基本数据类型和引用数据类型而是数组的时候(例如多个线程修改数组中的值)我们可以采用上述的三个原子操作的类进行原子操作。
# 5.字段更新器
AtomicReferenceFieldUpdater // 域 字段
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater 利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常。字段更新器要保护的字段一定要用volatile修饰,因为CAS必须结合volatile保证可见性。
# 常用原子操作中方法其实都差不多,具体场景使用具体类型和方法即可。具体方法使用以后介绍。