CAS
CAS(Compare and swap || Compare and exchange)比较交换,属于乐观锁的一种实现,当操作失败时并不会被操作系统挂起,而是会再次发起请求直到成功,CAS是天生免疫死锁的
看jdk中一个使用CAS的栗子
/**
* @Author tan
**/
public class Test {
# 自增 AtomicInteger是jdk1.5出现的基于CAS的原子变量类
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger();
i.incrementAndGet();
}
}
利用IDE查看源码
# Unsafe.class
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
主要是代码中的compareAndSwapInt,compareAndSwapInt其实是一个native的实现,简而言之就是java里面调c++或者其他编程语言
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
介绍
CAS 是一种有名的无锁算法,CAS是一种CPU级别的指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令。顾名思义这个算法分为两部分,比较和交换,一共有三个数据:
- V表示准备要被更新的变量,volatile修饰保证可见性
- E表示我们提供的 期望的值
- N表示新值 ,准备更新V的值
本质实现
CAS在底层上存在一条硬件级别的汇编指令
# cmpxchg指令是非原子性的 其原子性体现在lock,lock指令在执行时
# 锁定了一个北桥信号,并不采用锁总线的信号方式
lock cmpxchg
ABA问题及其解决方案
在CAS的核心算法中,通过死循环不断获取最新的E。如果在此之间,V被修改了两次,但是最终值还是修改成了旧值V,这个时候,就不好判断这个共享变量是否已经被修改过。为了防止这种不当写入导致的不确定问题,原子操作类提供了一个带有时间戳的原子操作类 AtomicStampedReferenc 看网上一个实例代码:
public class ABA {
private static AtomicInteger atomicInt = new AtomicInteger(100);
private static AtomicStampedReference<Integer> atomicStampedRef =
new AtomicStampedReference<Integer>(100, 0);
public static void main(String[] args) throws InterruptedException {
Thread intT1 = new Thread(new Runnable() {
@Override
public void run() {
atomicInt.compareAndSet(100, 101);
atomicInt.compareAndSet(101, 100);
}
});
Thread intT2 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean c3 = atomicInt.compareAndSet(100, 101);
System.out.println(c3); //true
}
});
intT1.start();
intT2.start();
intT1.join();
intT2.join();
Thread refT1 = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedRef.compareAndSet(100, 101,
atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
atomicStampedRef.compareAndSet(101, 100,
atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
}
});
Thread refT2 = new Thread(new Runnable() {
@Override
public void run() {
int stamp = atomicStampedRef.getStamp();
System.out.println("before sleep : stamp = " + stamp); // stamp = 0
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after sleep : stamp = " + atomicStampedRef.getStamp());//stamp = 1
boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);
System.out.println(c3); //false
}
});
refT1.start();
refT2.start();
}
# 输出如下:
true
before sleep : stamp = 0
after sleep : stamp = 2
false
查看对象内存布局小工具
Jot
# https://mvnrepository.com/artifact/org.openjdk.jol/jol-core
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("-----------------------------------------");
o = "谭婧杰";
System.out.println(ClassLayout.parseInstance(o).toPrintable());
对象在内存中的存储布局
java -XX:+PrintCommandLineFlags -version
# initialHeapSize -> 起始堆大小
# MaxHeapSize -> 最大堆大小
# useCompressedClassPointers - > 压缩指针
# useComressedOops -> 普通对象指针
按照上面的例子,给o加锁
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
由图可知,关于锁的信息一般都是记录在对象头里
Synchroized
Synchroized加锁
1、 修饰实例方法,作用于当前实例加锁,进入同步代码前 要获得当前实例的锁
2、 静态方法,作用于当前类对象加锁,进入同步代码前要 获得当前类对象的锁
3、 修饰代码块,指定加锁对象,对给定对象加锁,进入同 步代码库前要获得给定对象的锁。
4种锁状态,级别由低到高依次为:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几个状态会随着竞争情况逐渐升级