JUC包中除了锁,还提供了原子操作类来实现线程对临界资源的互斥访问。
Atomic包中提供了多种类型的原子操作类:
它们都是CAS(compareAndSwap)来实现原子性。
1.原子基本类型
用于原子更新基本类型,包括以下三类:
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整数类型
- AtomicLong:原子更新长整数类型
AtomicInteger 常用方法:
- int incrementAndGet():将当前值加1并返回新值。
- int decrementAndGet():将当前值减1并返回新值
- int updateAndGet(IntUnaryOperator updateFunction):将当前值原子执行用户自定义的操作后并返回新值。(updateFunction是用户自定义操作)
public class AtomicIntegerTest {
public static void main(String[] args) {
AtomicInteger integer = new AtomicInteger(0);
//递增
System.out.println("递增后的值: " + integer.incrementAndGet());
//递减
System.out.println("递减后的值: " + integer.decrementAndGet());
//增加20
System.out.println("增加20后的值: " + integer.updateAndGet((x)-> x + 20));
}
}
运行结果:
递增后的值: 1
递减后的值: 0
增加20后的值: 20
2.原子引用类型
原子基本类型只能保证基本类型的原子性,如果要原子更新一个对象就需要使用原子引用类型。
原子引用类型如下:
- AtomicReference:原子更新引用类型
- AtomicMarkableReference:AtomicMarkableReference跟AtomicStampedReference差不多,AtomicStampedReference关心的是动过几次,AtomicMarkableReference关心的是有没有被人动过。
- AtomicReferenceFieldUpdater:原子更新用类型中的字段。
- AtomicStampedReference:原子更新引用类型,只是该引用是带版本号的,用于解决ABA问题。
AtomicReference 主要方法:
- V get():获取当前引用的值。
- boolean compareAndSet(V expect, V update):如果当前引用等于预期引用,则将当前引用更新为新值。
下面以多个线程更新账户余额的例子来说明AtomicReference的使用
/*账户余额*/
class AccountBalance {
private BigDecimal banlance;
public AccountBalance(BigDecimal banlance) {
this.banlance = banlance;
}
public BigDecimal getBanlance() {
return banlance;
}
public void setBanlance(BigDecimal banlance) {
this.banlance = banlance;
}
}
class UpdateBalanceRunnable implements Runnable {
private AccountBalance accountBalance;
private AtomicReference<AccountBalance> reference;
public UpdateBalanceRunnable(AtomicReference<AccountBalance> reference,
AccountBalance accountBalance) {
this.reference = reference;
this.accountBalance = accountBalance;
this.reference.set(this.accountBalance);
}
@Override
public void run() {
while(true) {
AccountBalance prev = reference.get();
// 账户余额加10
AccountBalance next = new AccountBalance(prev.getBanlance().
add(new BigDecimal(10.0)));
if (reference.compareAndSet(prev, next)){
//更新成功,退出,否则自旋
break;
}
}
}
}
public class AtomicReferenceTest {
public static void main(String[] args) throws InterruptedException {
AccountBalance first = new AccountBalance(new BigDecimal(0));
System.out.println("更新前账户余额: " + first.getBanlance());
AtomicReference<AccountBalance> reference = new AtomicReference<>(first);
UpdateBalanceRunnable runnable = new UpdateBalanceRunnable(reference,first);
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("更新后账户余额: " + reference.get().getBanlance());
}
}
运行结果:
更新前账户余额: 0
更新后账户余额: 20
AtomicStampedReference主要方法:
- V get(int[] stampHolder):获取指定版本的值
- public V getReference():返回当前版本的值,也就是最新值。
- public int getStamp():返回当前版本戳。
- public boolean compareAndSet(V expectedReference, V newReference,
int expectedStamp, int newStamp):如果当前引用 等于 预期值并且 当前版本戳等于预期版本戳, 将更新新的引用和新的版本戳到内存 - public boolean attemptStamp(V expectedReference, int newStamp):如果当前引用 等于 预期引用, 将更新新的版本戳到内存
- public void set(V newReference, int newStamp) :设置当前引用的新引用和版本戳
public class AtomicStampedReferenceTest {
public static void main(String[] args) {
Integer integer1 = 0;
Integer integer2 = 2;
AtomicStampedReference<Integer> reference = new AtomicStampedReference<Integer>(integer1,1);
reference.compareAndSet(integer1,integer2,reference.getStamp(),reference.getStamp()+1);
System.out.println("更新value后:" + reference.getReference());
boolean b = reference.attemptStamp(integer2, reference.getStamp() + 1);
System.out.println("更新版本后: "+reference.getStamp());
boolean c = reference.compareAndSet(integer2,3,4, reference.getStamp()+1);
if (!c) {
System.out.println("CAS 操作失败");
}
}
}
运行结果:
更新value后:2
更新版本后: 3
CAS 操作失败
3.原子更新数组类型
Atomic包同样提供了对数组类型原子操作的类,主要包括:
- AtomicIntegerArray:整型数组原子操作类
- AtomicLongArray:长整型数组原子操作类
- AtomicReferenceArray:对象数组原子操作类
AtomicIntegerArray主要方法:
- int incrementAndGet(int i):将数组位置i的元素加1
- boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值
下面是一个多线程更新数组的例子。
public class AtomicIntegerArrayTest {
public static void main(String[] args) throws InterruptedException {
AtomicIntegerArray array = new AtomicIntegerArray(10);
List<Thread> list = new ArrayList<Thread>();
System.out.println("数组未修改前:");
for (int i = 0; i < array.length(); ++i) {
System.out.print(array.get(i) + "\t");
}
for (int i = 0; i < 10; ++i) {
Thread t = new Thread(() -> {
//依次更新数组
for (int j = 0 ; j < array.length(); ++j) {
array.incrementAndGet(j);
}
});
list.add(t);
t.start();
}
for (Thread t: list) {
t.join();
}
System.out.println("\n数组被修改后:");
for (int i = 0; i < array.length(); ++i) {
System.out.print(array.get(i) + "\t");
}
}
}
运行结果:
数组未修改前:
0 0 0 0 0 0 0 0 0 0
数组被修改后:
100 100 100 100 100 100 100 100 100 100