目录
(1)创建线程计算 1 + 2 + 3 + ... + 1000, 不使用 Callable 版本
(2) 创建线程计算 1 + 2 + 3 + ... + 1000, 使用 Callable 版本
3、ReentrantLock 和 synchronized 的区别
一、CAS
1、CAS的简单介绍
全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
● 比较 A 与 V 是否相等。(比较)
● 如果比较相等,将 B 写入 V。(交换)若不相等,说明当前线程的值A已经过时(主内存发生了变化),就将主内存的最新值V保存到工作内存中,此时无法将B写回主内存
● 返回操作是否成功。
(V是当前主内存值,A是当前工作内存值,B是当前线程想要修改的值)
2、CAS的实现
针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:
java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg; Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子 性。
简而言之,是因为硬件予以了支持,软件层面才能做到。
3、CAS的应用
(1)CAS实现原子类
标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的.
public class AtomicTest {
class Counter{
//基于整型的原子类
AtomicInteger count=new AtomicInteger();
}
public static void main(String[] args) {
AtomicInteger count=new AtomicInteger();
//等同于 ++i
System.out.println(count.incrementAndGet());
//等同于 i++
System.out.println(count.getAndIncrement());
//等同于 --i
System.out.println(count.decrementAndGet());
//等同于 i--
System.out.println(count.getAndDecrement());
}
可以看出来,下面的代码完全没有用到锁,但是依旧是线程安全的
public class Atomic2 {
static class Counter {
AtomicInteger count = new AtomicInteger();
void increase() {
//++i 使用CAS机制保证变量的原子性
count.incrementAndGet();
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter.increase();
}
});
t1.start();;
t2.start();
t1.join();
t2.join();
System.out.println(counter.count.get());
}
}
工作流程:
线程1执行cas操作,要将最新值1写回主内存,此时V==0、A==0,B==1,由于cas(V==A),所以就将1写回主内存,线程2执行cas操作,将最新值1写回主内存,此时V==1,A==0,B==1,cas(C!=A),说明主内存在线程2读取时已经有过修改,本次写回操作失败,将主内存最新值1加载到当前工作内存中,再次尝试以上操作,此时V==1,B==1,B==2,cas(V==A),就可以将最新值2写回到主内存
(2)实现自旋锁
自旋锁就是获取锁失败的线程不进入阻塞态,而是在CPU上空转(线程不让出CPU,而是跑一些无用的指令),不断查询当前锁的状态。
this.ower:表示当前获取锁的线程
null:期望获取锁的线程为null,表示当前自旋锁没有被任何线程持有
只有当this.ower==null时,就尝试将当前锁this.ower==Thread.cuurrentThread(),将持有锁的线程设置为当前线程
4、CAS引发的ABA问题
(1)ABA问题的解释
ABA 的问题:
假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A.
接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要
先读取 num 的值, 记录到 oldNum 变量中.
使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z.
但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A