CAS就是compare and swap(比较交换),是一种很出名的无锁的算法,就是可以不使用锁机制实现线程间的同步。使用CAS线程是不会被阻塞的,所以又称为非阻塞同步。CAS算法涉及到三个操作:三个操作数——内存位置、预期原值及新值
需要读写内存值V;进行比较的值A;准备写入的值B
当且仅当V的值等于A的值等于V的值的时候,才用B的值去更新V的值,否则不会执行任何操作(比较和替换是一个原子操作-A和V比较,V和B替换),一般情况下是一个自旋操作,即不断重试
特别注意:
标准库 java.util.concurrent.atomic 中的类,它们都是使用 CAS(Compare-And-Swap)技术实现的:
例如 AtomicInteger 类,这些类本身就是原子的,因此相关操作即使在多线程下也是安全的:
测试原子类:
public class CASTest {
public static void main(String[] args) throws InterruptedException {
// 创建原子类,初始化值为0
AtomicInteger num = new AtomicInteger(0);
Thread t1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
// num++ 操作
num.getAndIncrement();
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
num.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(num);
}
}
num.getAndIncrement();操作伪代码:
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) {
int currentValue = num.get();
int incrementedValue = num.incrementAndGet();
System.out.println("Current value before increment: " + currentValue);
System.out.println("Incremented value: " + incrementedValue);
}
}
num.get()
用于获取当前值,num.incrementAndGet()
用于增加当前值并获取增加后的值。这两个操作是原子操作,也就是说,它们不会被线程调度机制打断。因此,num.getAndIncrement()
操作可以在多线程环境中安全使用。
代码实现:
import java.util.concurrent.atomic.AtomicBoolean;
public class SpinLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public void lock() {
// 自旋锁的核心部分:一直循环尝试获取锁
while (!locked.compareAndSet(false, true)) {
// 等待,直到可以获取锁
Thread.yield();
}
}
public void unlock() {
locked.set(false);
}
}
原理说明:lock
方法尝试将locked
变量的值从false
更改为true
。如果成功,那么线程就获得了锁,可以继续执行。如果失败(也就是说,locked
变量的值已经是true
),那么线程就会在循环中等待,直到它可以获得锁。unlock
方法将locked
变量的值设回false
,释放锁。
CAS操作存在一个称为ABA问题。这个问题源于CAS操作的特性:在执行CAS操作时,如果内存位置V的值被其他线程更改,然后再次更改回原始值A,那么对于执行CAS操作的线程来说,内存位置V的值仍然与预期值A匹配,因此它会错误地认为没有其他线程修改过该值。
假设有一个共享变量count
,初始值为0。有两个线程A和B,它们都执行以下操作:
count
的值;count
的值;count
的新值写入内存。假设线程A先读取count
的值为0,然后增加它的值为1,并尝试使用CAS操作将新值1写入内存。此时,如果线程B先读取count
的值为0,增加它的值为1,然后再次将count
的值更改回0,并尝试使用CAS操作将新值0写入内存。由于线程B的CAS操作成功,count
的值变为0,但是实际上count
的值应该为1。
解决方案:可以给每个变量加上一个版本号。当变量被修改时,版本号自增。在执行CAS操作时,除了比较变量的值是否与预期值匹配外,还需要比较版本号是否一致。如果版本号不一致,则说明变量已经被其他线程修改过,需要重新读取变量的最新值并重新尝试CAS操作。
结合以上的锁策略,我们就可以总结出,Synchronized 具有如下特性:
从上述synchronized锁具有的策略可知,synchronized锁可根据实际场景进行锁升级,在JVM中对synchronized主要有以下锁升级策略:
synchronized 锁升级策略是Java6之后引入的概念,在Java6之前只有两种锁状态:无锁和重量级锁;当一个线程获得锁时,其他线程需要等待该线程释放锁后才能继续执行。这种策略存在一些问题,例如死锁和性能问题。
为了解决这些问题,Java 6 引入了偏向锁、轻量级锁和重量级锁三种锁升级策略。这些策略的目的是在保证线程安全的前提下,尽可能减少锁的竞争和系统的开销。