在 Java锁机制1.0中已经介绍了 锁 这种抽象概念,Java如何实现互斥锁(synchronized)、互斥锁的工作原理以及互斥锁的优化 及 锁的四种状态及其转化条件。
1.为什么需要CAS
悲观: 操作系统会悲观的认为如果不严格同步线程调用,就一定会造成异常。
悲观锁: 当有多个线程要操作同一对象,如果使用互斥锁,但是它的同步方式是悲观的,所以互斥锁将会锁定资源,只供一个线程调用,而阻塞其他线程——悲观锁。
悲观锁不适用场景1: 当大部分调用都是读操作(线程安全问题都是对全局/静态变量进行写操作造成的),那么就没有必要在每次调用的时候都锁定资源。
悲观锁不适用场景2: 同步代码块执行的耗时远远小于线程切换的耗时。
那么,如何不对共享资源进行锁定,也能对线程调用进行协调呢?
于是,CAS(Compare And Swap) 算法就诞生了。
2.啥是CAS?
通过下列场景进行描述CAS:两个线程争夺一个对象资源的情况。
注意: 上述的比较值和切换值同一时间只能有一个线程进行操作 → CAS必须是原子性的
如何实现CAS的原子性?
当然不能通过锁
各种不同的4架构的CPU都提供了指令级别的CAS原子操作,如X86(cmpxchg
支持CAS)、ARM(LL/SC
),而不需要通过操作系统的同步原语(如mutex
),CPU已经原生支持了CAS,上层调用即可,这样就能不依赖锁来进行线程同步。但这样并不意味着CAS就能代替锁。
这些通过CAS来实现同步的工具,由于不会锁定资源。而且当线程需要修改共享资源时,总是会乐观地认为对象状态值没有被其他线程修改过,而是每次自己都会主动尝试去比较状态值,相较于悲观锁,这种机制被称为 乐观锁 。但这是一种无锁的同步机制。
3.Java如何实现CAS
案例: 使用3条线程,将一个值,从0累加到1000
先演示不用锁,也不用CAS来进行
public class TestDemo1 {static int num &#61; 0;public static void main(String[] args) {for (int i &#61; 0; i < 3; i&#43;&#43;) {Thread thread &#61; new Thread(() -> {while (true){if (num >&#61; 1000){break;}System.out.println("thread name: " &#43; Thread.currentThread().getName() &#43; ":" &#43; num&#43;&#43;);}});thread.start();}}
}
查看控制台输出&#xff1a;
出现了重复打印的结果
接着演示&#xff0c;使用互斥锁&#xff08;悲观锁&#xff09;来改进&#xff1a;
public class TestDemo1 {static int num &#61; 0;public static void main(String[] args) {for (int i &#61; 0; i < 3; i&#43;&#43;) {Thread thread &#61; new Thread(() -> {while (true){synchronized (TestDemo1.class){ if (num >&#61; 1000){break;}System.out.println("thread name: " &#43; Thread.currentThread().getName() &#43; ":" &#43; num&#43;&#43;);}}});thread.start();}}
}
使用CAS
public class TestDemo1 {static AtomicInteger num &#61; new AtomicInteger(0);public static void main(String[] args) {for (int i &#61; 0; i < 3; i&#43;&#43;) {Thread thread &#61; new Thread(() -> {while (true){if (num.get() >&#61; 1000){break;}System.out.println("thread name: " &#43; Thread.currentThread().getName() &#43; ":" &#43; num.incrementAndGet());}});thread.start();}}
}
4.AtomicInteger是如何实现CAS同步的&#xff1f;
使用 Unsafe.compareAndSwapInt 进行更新
进入源码&#xff1a;
public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID &#61; 6214790243416807050L;private static final Unsafe unsafe &#61; Unsafe.getUnsafe();private static final long valueOffset;
找到incrementAndGet()
public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) &#43; 1;}
进一步点进getAndAddInt()
可以看到&#xff0c;其调用了compareAndSwapInt()
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 &#61; this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 &#43; var4));return var5;}
注意&#xff1a; 此处的自旋并不会一直循环下去&#xff0c;其默认最大循环次数为10
5.了解unsafe类
java不能直接访问操作系统底层&#xff0c;而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作&#xff0c;主要提供了以下功能&#xff1a;
- 通过Unsafe类可以分配内存&#xff0c;可以释放内存
- 可以定位对象某字段的内存位置&#xff0c;也可以修改对象的字段值&#xff0c;即使它是私有的
- 挂起与恢复&#xff08;
park
挂起线程、unpark
恢复挂起的线程&#xff09; - CAS操作&#xff08;通过
compareAndSwapXXX
方法实现&#xff09;
CAS操作有3个操作数&#xff0c;内存值M&#xff0c;预期值E&#xff0c;新值U&#xff0c;如果M&#61;&#61;E&#xff0c;则将内存值修改为B&#xff0c;否则啥都不做。