1. 共享模型之无锁
管程即monitor是阻塞式的悲观锁实现并发控制,同样可以非阻塞式的乐观锁的来实现并发控制
1.1 问题提出
有如下需求,保证account.withdraw取款方法的线程安全
public class Test5 {
public static void main(String[] args) {
Account.demo(new AccountUnsafe(10000));
}
}
class AccountUnsafe implements Account {
private Integer balance;
public AccountUnsafe(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
return balance;
}
@Override
public void withdraw(Integer amount) {
// 通过这里加锁就可以实现线程安全,不加就会导致结果异常
synchronized (this){
balance -= amount;
}
}
}
interface Account {
// 获取余额
Integer getBalance();
// 取款
void withdraw(Integer amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(Account account) {
List<Thread> ts = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(10);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance()
+ " cost: " + (end-start)/1000_000 + " ms");
}
}
解决思路-无锁
上面的代码中可以使用synchronized加锁操作来实现线程安全,但是synchronized加锁操作太耗费资源,这里我们使用无锁来解决此问题
class AccountSafe implements Account{
AtomicInteger atomicInteger ;
public AccountSafe(Integer balance){
this.atomicInteger = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
return atomicInteger.get();
}
@Override
public void withdraw(Integer amount) {
// 核心代码
while (true){
int pre = getBalance();
int next = pre - amount;
if (atomicInteger.compareAndSet(pre,next)){
break;
}
}
// 可以简化为下面的方法
// balance.addAndGet(-1 * amount);
}
}
compareAndSet(pre, next)
会比较当前atomicInteger
对象的值是否等于pre
,如果相等,则将其更新为next
。这个操作是原子的,即保证在多线程环境下不会被打断,防止并发冲突。
1.2 CAS 与 volatile
1.2.1 CAS
CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。
CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令
前面看到的AtomicInteger的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?
@Override
public void withdraw(Integer amount) {
// 核心代码
// 需要不断尝