Java拾遗 - CAS算法以及immutable变量的线程安全

时间:2021-02-01 18:15:48

综述CAS

1. 锁的机制

为了实现线程安全,对于一些关键变量必须加锁

常用的锁机制有两种:

1、悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。悲观锁的实现,往往依靠底层提供的锁机制;悲观锁会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

2、乐观锁:假设不会发生并发冲突,每次不加锁而是假设没有冲突而去完成某项操作,只在提交操作时检查是否违反数据完整性。如果因为冲突失败就重试,直到成功为止。乐观锁大多是基于数据版本记录机制实现。为数据增加一个版本标识,比如在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

锁机制存在以下问题:

(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

在JAVA1.5之前,JAVA语言是靠Synchronzied关键字保证锁同步,这是一种悲观锁,效率低且易于死锁,有没有一种锁,能够在资源被锁时也不影响其他进程?

CAS

JAVA1.6之后,java.util.concurrent(J.U.C)种提供的atomic包中的类,使用的是乐观锁,用到的机制就是CAS,CAS(Compare and Swap)有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做

以AtomicInteger包为例

  /**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/

public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

可见实现CAS算法的部分是来自于UNSAFE这个包

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

再来到UNSAFE包中,发现这个方法是一个native方法,网上其他教程甚至翻到了系统汇编语言,这里我认为理解这个CAS的实现原理就好,而底层上他就是利用了CPU的一个快捷计算方式。

那么我们开始使用CAS实现一个乐观锁锁

首先是一组不带锁的1234输出
代码实现如下:

 //unsafe
private static int flagnum__ = 0;

static class task__ implements Runnable{

@Override
public void run() {
while (flagnum__ < 100) {
flagnum__++;
System.out.println(flagnum__);
}
}
}
public static void main(String args[]) {
Executor executor = Executors.newFixedThreadPool(2);
executor.execute(new task__());
executor.execute(new task__());
}

输出结果如下:

1
3
4
5
6
7
8
9
...
````
可见由于线程不安全的问题,很多的数字都跳过去了。
那我们利用CAS算法实现一个”稳定“的乐观锁





<div class="se-preview-section-delimiter"></div>
//Thread safe now?
private static boolean compareAndSet(int old_, int new_) {
if (old_ != flagnum__) {
return false;
} else {
flagnum__ = new_;
return true;
}
}
static class task2__ implements Runnable{
@Override
public void run() {
while (flagnum__ < 100) {
int old_val = flagnum__;
if (compareAndSet(old_val, old_val + 1))
System.out.println(flagnum__);
}
}
}
public static void main(String args[]) {
Executor executor = Executors.newFixedThreadPool(2);

// executor.execute(new task__());
// executor.execute(new task__());
executor.execute(new task2__());
executor.execute(new task2__());
}

结果如下




<div class="se-preview-section-delimiter"></div>

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2

k可见依然是一个线程不安全的,依然无法实现乐观锁。看来Integer是无法实现乐观锁的(?),接下来我们运用Unsafe里面的类去实现一个乐观锁,即调用JVM底层的实现,并与Synchronized关键字实现的悲观锁的效率做一个对比





<div class="se-preview-section-delimiter"></div>

这里写代码片
“`