Day11 多线程进阶 9 CAS 原子引用
uwupu 啦啦啦啦啦

CAS

介绍

比较当前工作内存中的值和主内存的值,若这个值为期望值,则执行操作。若不是就一直循环,即阻塞。

public final boolean compareAndSet(int expect, int update)

public final boolean compareAndSet(int expect, int update)是一个AtomicInteger的实例方法。

若实例的值为expect,则赋值为update。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(123);
//public final boolean compareAndSet(int expect, int update)
//expect 期望 update 更新
//若达到期望值,则更新,否则不更新
System.out.println("1. "+atomicInteger);
System.out.println(atomicInteger.compareAndSet(123, 345));
System.out.println("2. "+atomicInteger);
System.out.println(atomicInteger.compareAndSet(234,123));
System.out.println("3. "+atomicInteger);
}
}
1
2
3
4
5
6
7
1. 123
true
2. 345
false
3. 345

Process finished with exit code 0

atomicInteger的初始值为123,经过第一次操作,123变为345,方法返回值为true,表示成功。

后来atomicInteger的值为345,第二次操作,若值为234则变为123,显然不是345,返回false,修改失败。

值仍为345。

剖析底层的CAS

getAndIncrement()解析

1
2
3
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

方法getAndIncrement()可以为AtomicInteger对象通过内存方式实现“++”,其中,getAndIncrement()方法调用了getAndAddInt()方法,其中,为this即为当前对象,valueOffset为”对象为内存中的偏移值”(详见“其他”章节Unsafe类),“1”为增加的大小。

getAndAddInt()解析

image

CAS总结

CAS:比较当前工作内存中的值和主内存的值,若这个值为期望值,则执行操作。若不是就一直循环,即阻塞。

缺点:

  1. 循环耗时;
  2. 一次性只能保证一个共享变量的原子性;
  3. ABA问题。

CAS: ABA问题(狸猫换太子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo33_ABA问题 {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(123);

// ===================== 捣乱的线程 =====================
System.out.println(atomicInteger.compareAndSet(123,321));
System.out.println(atomicInteger);
System.out.println(atomicInteger.compareAndSet(321,123));
System.out.println(atomicInteger);
// ===================== 期望的线程 =====================
System.out.println(atomicInteger.compareAndSet(123,321));
System.out.println(atomicInteger);

/**
* 期望的线程不知道捣乱的线程改过数据。
*/
}
}

原子引用

AtomicStampedReference

可以原子更新的对象引用。 思想:乐观锁

AtomicStampedReference介绍

AtomicStampedReference创建的对象,有功能:

  1. 指向一个泛型对象;
  2. 记录一个stamp值(类似版本号)。

通过compareAndSet()方法可以修改AtomicStampedReference对象的指向。在使用该方法更新指向时,可以指定原对象、修改后对象、期望stamp值、目标stamp值

该方法会判断指定的“原对象”“期望stamp值”是否与存储的一致,

  • 若一致,则修改指向和stamp值,返回true
  • 若不一致,则不修改,返回false

在线程执行前,记录对象的stamp值,然后进行业务代码。在这期间,若对象被修改,会留下stamp更新。再次修改对象前,核对stamp值,可以得知对象是否在这期间修改过。

例子

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
public class Demo34_AtomicReference {
public static void main(String[] args) {
//注意,如果泛型是一个包装类,注意对象的引用问题。
//正常业务里,这里应为一个对象
AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<>(12,1);
//捣乱的线程
//负责在执行期间,修改atomicReference的值,并更新stamp的值。
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"1-> "+atomicReference.getStamp());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A "+atomicReference.compareAndSet(
12, 123,
atomicReference.getStamp(),
atomicReference.getStamp() + 1
));
System.out.println(Thread.currentThread().getName()+"2-> "+atomicReference.getStamp());

System.out.println("A "+atomicReference.compareAndSet(
123, 12,
atomicReference.getStamp(),
atomicReference.getStamp() + 1
));

System.out.println(Thread.currentThread().getName()+"3-> "+atomicReference.getStamp());
},"A").start();
//正常的业务线程
//业务代码执行前,记录stamp的值,业务代码后,核对stamp值是否正确,若正确则修改对象的值,错误则不修改。
new Thread(()->{
int stamp = atomicReference.getStamp();
System.out.println(Thread.currentThread().getName()+"1-> "+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B "+atomicReference.compareAndSet(
12, 23,
stamp,
stamp + 1
));
System.out.println(Thread.currentThread().getName()+"2-> "+atomicReference.getStamp());
},"B").start();
}
}

其他

Unsafe类

Unsafe用于让Java操作内存。

原理:Java不能操作内存,但C++可以操作内存。Java有个Unsafe类有native本地方法,可以调用C++操作内存。

image

int的包装类Interger

Integer使用了对象缓存机制,默认范围 -128 ~ 127。

推荐使用静态工厂方法 valueOf 获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。

对于Integer var = ?在**-128 ~ 127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象这个区间内的Integer值可以直接使用“==”进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象。推荐使用equals**方法进行判断。

 评论