管程即monitor是阻塞式的悲观锁实现并发控制,同样可以非阻塞式的乐观锁的来实现并发控制
有如下需求,保证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
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
。这个操作是原子的,即保证在多线程环境下不会被打断,防止并发冲突。
CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。
CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令
前面看到的AtomicInteger的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?
- @Override
- public void withdraw(Integer amount) {
- // 核心代码
- // 需要不