while (抢锁(lock) == 失败) {}
如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止.
此处公平的判定为 “先来后到”
// 第一次加锁, 加锁成功
lock();
// 第二次加锁
lock();
比如上述代码,第一次加锁后,如果第二次不能继续加锁,那么这个锁就是不可重入锁.会阻塞等待.
如果可以继续加锁,那么就是可重入锁.
Java中只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括 synchronized关键字锁都是可重入的.而 Linux 系统提供的 mutex 是不可重入锁.
compare and swap 比较和交换
下面写的代码不是原子的, 真实的 CAS 是一个原子的硬件指令完成的. 这个伪代码只是辅助理解 CAS 的工作流程.
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
CAS就是根据一个值是否改变来判定,是否被操作.
针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:
简而言之: 是因为硬件予以了支持,软件层面才能做到原子性.
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就自旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}
通过一个循环来实现的,循环里面调用CAS,CAS会比较当前的owner值是否是null,如果是null 就改成当前线程,意思就是当前线程拿到了锁,如果不是null就放回false 进入下次循环,下次循环仍然是进行CAS操作,如果当前这个锁一直被别人持有,当前尝试加锁的线程 就会在这个 while 的地方快速反复进行循环 -> 自旋(忙等)
虽然是忙等 但是这里自旋锁是轻量级锁,也可以视为乐观锁.当前这个锁虽然没能立即拿到,但是预期很快就会拿到(假设锁冲突不激烈).短暂的自旋几次,浪费点CPU问题都不大,好处就是只要这边锁一释放,就能立即拿到锁.
标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的. 典型的就是 AtomicInteger 类.
public class Test {
/**
* Java中原子类的 CAS
* AtomicInteger num = new AtomicInteger(0);
* num.decrementAndGet();//--num
* num.incrementAndGet();//++num
* num.getAndIncrement();//num++
* num.getAndDecrement();//num--
* num.getAndAdd(10);//num += 10;
*/
public static void main(String[] args) throws InterruptedException {
AtomicInteger num = new AtomicInteger(0);
Thread t1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
num.getAndIncrement();//num++
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
num.getAndIncrement();
}
});
//在这里 不会有线程安全问题 基于 CAS实现++ 操作
//这里就可以保证既能线程安全,又能比synchronized高效
//因为 CAS不涉及线程阻塞等待
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(num.get());
}
}
根据 2.1 的伪代码流程来解释
经过了上述流程,(&address)的值已经被改变过了两次.但是比较的时候,在代码看来是没有改变过的.会继续进行正常的操作.这就是 CAS中的 ABA问题.
给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时也要比较版本号是否符合预期.