java中的CAS乐观锁
最近,总是听到同事在面试的时候问候选人java中的锁相关的知识,大部分同学在问到CAS的时候会有些一知半解;
1. 原子操作
说到原子操作,会想到数据库事务中的原子性,道理都差不多,指一行或多行代码要么都执行成功或失败。
比如:i++这行代码,在执行的过程中会分为三步去执行:
1.取出i的值;
2.将i的值+1;
3.将+1后的赋值给i;
在单线程的情况下,这种操作不会有问题,但是多线程的情况下呢:
出现了线程B的结果将线程A的结果覆盖的情况;那就可以说i++不是原子操作;
可以本地验证下是不是这样的:
private static int count = 0; public static void add() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } count++; } public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(100); for(int i=0;i<100;i++){ new Thread(() -> { add(); countDownLatch.countDown(); }).start(); } countDownLatch.await(); System.out.println("计算结果(Count):"+count); }
我这里面运行的结果:计算结果(Count):92
2.什么是CAS
Conmpare And Swap,比较和交换,实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。
上面这段话为官方解释用语,什么意思呢?
CAS操作包括三个操作数:
- 内存位置 V
- 预期原值 A
- 新值 B
如果内存位置V与预期原值A相等,则认为没有被其它线程修改过,认为是安全的,那么处理器会自动将内存位置V的值更新为新值B;
如果内存位置V与预期原值A不相等,则处理器不做任何操作;
3.Java中的CAS实现
java中的CAS锁是通过Unsafe类实现,但是方法都是native;查看openJDK可以在里面找到Unsafe.cpp源码,最后调用的是:Atomic:comxchg();
对于cmpxchg指令,程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果是多处理器,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,就省略lock前缀;
关于lock前缀:
1.确保对内存的读-改-写操作原子执行。
2.禁止该指令与之前和之后的读和写指令重排序。
3.把写缓冲区中的所有数据刷新到内存中。
上面的示例代码通过CAS来实现:
private static AtomicInteger count = new AtomicInteger(0); public static void add() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } count.incrementAndGet(); } public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(100); for(int i=0;i<100;i++){ new Thread(() -> { add(); countDownLatch.countDown(); }).start(); } countDownLatch.await(); System.out.println("计算结果(Count):"+count.get()); }
无论运行多少次,结果都是:100
4.CAS中的ABA问题
假如现有两个线程:线程1与线程2,count=1;
线程1:将count+1;
线程2:将count+1,count-1;
ABA问题,就是线程1与线程2在执行的过程中,线程2将值由之前的A改为了B又改为了A,但此时线程1以为A还是之前的值,没有其它线程改变过,则线程1也做更新;
ABA模拟代码:
private static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // 让它加1 int expectNum = count.get(); int updateNum = expectNum + 1; boolean result = count.compareAndSet(expectNum, updateNum); System.out.println("操作成功/失败:"+ result+";count="+count.get()); }); Thread threadB = new Thread(() -> { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } count.incrementAndGet(); // +1 count.decrementAndGet(); // -1 }); threadA.start(); threadB.start(); }
这里的输出结果:操作成功/失败:true;count=1
解决ABA问题:
产生这种问题的原因是A-B-A时,没有一个标识来标记第一个A和第三个A是不是同一个A,如果能够加个版本号A1-2B-3A,每次变量更新的时候把版本号加一,这样就可以解决这个问题;
Java里面提供了AtomicStampedReference来解决这个问题;
private static AtomicStampedReference<Integer> reference = new AtomicStampedReference(new Integer(0), 1); public static void main(String[] args) { Thread threadA = new Thread(() -> { Integer expectedReference = reference.getReference(); Integer updateReference = expectedReference + 1; Integer expectedStamp = reference.getStamp(); Integer updateStamp = expectedStamp + 1; try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = reference.compareAndSet(expectedReference, updateReference, expectedStamp, updateStamp); System.out.println("操作成功/失败:" + result + ";count=" + reference.getReference()); }); Thread threadB = new Thread(() -> { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } // +1 Integer expectedReference1 = reference.getReference(); Integer expectedStamp1 = reference.getStamp(); reference.compareAndSet(expectedReference1, expectedReference1 + 1, expectedStamp1, expectedStamp1 + 1); System.out.println("1:"+reference.getReference()+":"+reference.getStamp()); // -1 Integer expectedReference2 = reference.getReference(); Integer expectedStamp2 = reference.getStamp(); reference.compareAndSet(expectedReference2, expectedReference2 - 1, expectedStamp2, expectedStamp2 + 1); System.out.println("2:"+reference.getReference()+":"+reference.getStamp()); }); threadA.start(); threadB.start(); }
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- Java 并发与多线程规范(4) 自旋锁是可重入的吗?CAS与自旋锁的关系,CAS的ABA问题,以及乐观锁,悲观锁,互斥锁,读写锁的整理
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- Java性能 -- CAS乐观锁
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- java 5多线程乐观锁CAS之于悲观锁synchronized
- Java多线程-线程安全思路拓展-Atomic数值类-Volatile以及CAS乐观锁
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- 【Java】CAS的乐观锁实现之AtomicInteger源码分析
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS
- 乐观锁与悲观锁,以及Java对CAS的支持
- JAVA CAS原理深度分析
- java并发前身CAS原理深度分析
- Java的多线程机制:缓存一致性和CAS
- sso-cas全攻略(java版)------部署cas client端应用
- JAVA CAS原理深度分析
- 0097 Java线程:CAS原子性【进阶】