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