Atomic

CAS及原子操作

概述

CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。CAS也是现在面试经常问的问题,本文将深入的介绍CAS的原理。

CAS与volatile

结合CAS和volatile可以实现无锁并发,适用于线程数少、多核CPU的场景下使用

volatile

获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。

它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是操作主存。即一个线程对volatile变量的修改,对另一个线程可见。

CAS必须借助volatile才能读取到共享变量的最新值来实现Compare-and-Swap的效果。

特点

优点:

  • 可以保证变量操作的原子性

  • 并发量低时,CAS效率高于synchronized

  • 在线程对共享资源占用时间较短的情况下,使用CAS机制效率也会较高

缺点:

  • 无法解决ABA问题

  • 可能会消耗较高的CPU

  • 不能保证代码块的原子性

乐观锁

​ 乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现);

​ 特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。

悲观锁

​ 顾名思义,悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的;

​ 特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;

原子包下的类

在java.util.concerrent.atomic包下面的类

原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch).

image-20230614170503809

原子整数

  • AtomicBoolean

  • AtomicInteger

  • AtomicLong

API

image-20230614171907381

原子引用

简介

JUC提供如下原子引用类:

  • AtomicReference:普通原子引用类型,对对象进行原子操作

  • AtomicStampedReference:带int类型版本戳的原子引用类型,记录更改次数

  • AtomicMarkableReference:带boolean类型版本戳的原子引用类型,记录是否更改

作用:保证引用类型的共享变量是线程安全的。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class DecimalAccountCAS implements DecimalAccount {
private AtomicReference<BigDecimal> balance;

public DecimalAccountCAS(BigDecimal balance) {
this.balance = new AtomicReference<>(balance);
}

@Override
public BigDecimal getBalance() {
return balance.get();
}

@Override
public void withdraw(BigDecimal amount) {
while (true) {
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(amount);
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
}

interface DecimalAccount {
BigDecimal getBalance();
void withdraw(BigDecimal amount);
}

ABA问题

​ ABA问题是,当一个线程对数据进行修改时为B时,先检查原来数据是不是A,而在检查期间,另外一个线程修改数据为B,然后对数据又修改为A的情况,这种情况为ABA为问题

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package Thread.Day05;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
* @author MR.XSS
* @version 1.0
* 2023/6/14 11:32
*/
public class Demo02 {
//带有版本号控制的引用原子类
static AtomicStampedReference<String> reference = new AtomicStampedReference<>("A", 0);

public static void main(String[] args) throws InterruptedException {
int stamp = reference.getStamp();
other();
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"-----------操作-----------");
System.out.println("操作前版本号----->"+stamp);
boolean flag = reference.compareAndSet(reference.getReference(), "B", stamp, stamp+1);
System.out.println("操作后版本号是------->"+reference.getStamp()+"操作:"+flag);
}


public static void other() {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"-----------操作-----------");
int stamp = reference.getStamp();
System.out.println("操作前版本号----->"+stamp);
boolean flag = reference.compareAndSet(reference.getReference(), "B", stamp, stamp+1);
System.out.println("操作后版本号是------->"+reference.getStamp()+"操作:"+flag);
}).start();

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"-----------操作-----------");
int stamp = reference.getStamp();
System.out.println("操作前版本号----->"+stamp);
boolean flag = reference.compareAndSet(reference.getReference(), "A", stamp, stamp+1);
System.out.println("操作后版本号是------->"+reference.getStamp()+"操作:"+flag);
}).start();
}
}

image-20230614172157233

原子数组

JUC提供如下原子数组类:

  • AtomicIntegerArray

  • AtomicLongArray

  • AtomicReferenceArray

作用:保证数组内的元素的线程安全。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Slf4j(topic = "AtomicArray")
public class AtomicArrayDemo {
public static void main(String[] args) {
demo(
() -> new int[10],
array -> array.length,
(array, index) -> array[index]++,
array -> log.debug("普通数组:{}", Arrays.toString(array))
);
demo(
() -> new AtomicIntegerArray(10),
AtomicIntegerArray::length,
AtomicIntegerArray::getAndIncrement,
array -> log.debug("安全数组:{}", array)
);
}

/**
* @param arraySupplier 提供数组
* @param lengthFunction 获取数组长度的方法
* @param putConsumer 指定元素的自增方法
* @param printConsumer 打印数组元素的方法
* @apiNote
* <p> Supplier 提供者 无中生有 () -> 结果 </p>
* <p> Function 函数 一个参数一个结果 (参数) -> 结果 | BiFunction (参数1,参数2) -> 结果 </p>
* <p> Consumer 消费者 一个参数没有结果 (参数) -> Void | BiConsumer (参数1,参数2) -> Void </p>
*/
private static <T> void demo(Supplier<T> arraySupplier, Function<T, Integer> lengthFunction,
BiConsumer<T, Integer> putConsumer, Consumer<T> printConsumer) {
List<Thread> list = new ArrayList<>();
T array = arraySupplier.get();
int length = lengthFunction.apply(array);

for (int i = 0; i < length; i++) {
list.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) { // 正确结果应该是数组元素都为10000
putConsumer.accept(array, j % length);
}
}));
}

list.forEach(Thread::start);
list.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
printConsumer.accept(array);
}
}

字段更新器

JUC提供如下字段更新器:

  • AtomicReferenceFeildUpdater:引用类型的属性

  • AtomicIntegerFieldUpdater:整形的属性

  • AtomicLongFeildUpdater:长整形的属性

注意:利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合volatile修饰的字段使用,否则会出现异常。

1
Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j(topic = "AtomicFieldUpdater")
public class AtomicFieldUpdaterDemo {
public static void main(String[] args) {
Student stu = new Student();
AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
log.debug("update ? {}", updater.compareAndSet(stu, null, "RubbishK"));
log.debug("update ? {}", updater.compareAndSet(stu, stu.getName(), "FlowerK"));
log.debug(stu.toString());
}
}

class Student {
volatile String name;

public String getName() { return name; }

@Override
public String toString() { return "Student[name='" + name + "']"; }
}

CAS锁

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package Thread.Day05;

import java.util.concurrent.atomic.AtomicInteger;

/**
* @author MR.XSS
* @version 1.0
* 注意:===>不能用于生产环境,不然就被炒鱿鱼了!!!
* doge!!!!
*/
public class CAS_Lock {
//标志状态位进行加锁操作
// 1 表示上锁 0 表示解锁
private AtomicInteger LOCK_STATE = new AtomicInteger(0);


public void setLock() {
while(true) if (LOCK_STATE.compareAndSet(0, 1)) break;
}

public void unLock() {
LOCK_STATE.compareAndSet(1, 0);
}

}