天下脸皮共十分
我占八分

CAS原理及应用

概念

CAS:compare and swap

CAS:Compare and set

无锁并发的优势

  • 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。打个比喻
  • 线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速恢复到高速运行,代价比较大
  • 但无锁情況下,因为线程要保持运行,需要额外CPU的支持,CPU在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。
  • CAS最好线程数小于CP U核心数

核心在于X86架构的CompareAndSwap原子操作

纯Java代码实现不了无锁并发

CAS的特点

结合CAS和 volatile可以实现无锁井发,适用于线程数少、多核CPU的场景下。

  • CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗
  • synchronized是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
  • CAS体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思
    • 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之
    • 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响

CAS应用实现

并发原子性问题
 @Slf4j
 @Getter
 class BankAccount {
     private Integer account;
 ​
     public BankAccount(Integer account) {
         this.account = account;
    }
 ​
     public void withdraw(int amount) {
         account -= amount;
    }
 ​
     public void concurrentWithdraw(int amount) throws InterruptedException {
         long begin = System.nanoTime();
         List<Thread> threads = new ArrayList<>();
         for (int i = 0; i < 1000; i++) {
             Thread t = new Thread(() -> {
                 withdraw(10);
            });
             threads.add(t);
        }
         for (int i = 0; i < threads.size(); i++) {
             threads.get(i).start();
        }
         for (int i = 0; i < threads.size(); i++) {
             threads.get(i).join();
        }
         long end = System.nanoTime();
         log.info("the left account is: {}", account);
         log.info("total time is: {}ms", (end - begin) / (1000 * 1000));
    }
 }
 ​
 public class CasDemo {
     public static void main(String[] args) throws InterruptedException {
         BankAccount bankAccount = new BankAccount(10000);
         bankAccount.concurrentWithdraw(10);
    }
 }

结果如下

 20:01:07.234 [main] INFO tech.ityoung.study.demo.jvm.juc.BankAccount - the left account is: 190
 20:01:07.238 [main] INFO tech.ityoung.study.demo.jvm.juc.BankAccount - total time is: 95ms
 ​
 Process finished with exit code 0
CAS改造

代码如下

主要将account更改类型为AtomicInteger

利用compareAndSet返回的Boolean值不断重试,直到返回为true结束循环

 @Slf4j
 class BankAccount {
     private AtomicInteger account;
 ​
     public BankAccount(Integer account) {
         this.account = new AtomicInteger(account);
    }
 ​
 /*   public void withdraw(int amount) {
         account -= amount;
     }*/
 ​
     public void withdraw(int amount) {
         while (true) {
             int init = account.get();
             int next = init - amount;
             if (account.compareAndSet(init, next)) {
                 break;
            }
        }
    }
 ​
     public void concurrentWithdraw(int amount) throws InterruptedException {
         long begin = System.nanoTime();
         List<Thread> threads = new ArrayList<>();
         for (int i = 0; i < 1000; i++) {
             Thread t = new Thread(() -> {
                 withdraw(10);
            });
             threads.add(t);
        }
         for (int i = 0; i < threads.size(); i++) {
             threads.get(i).start();
        }
         for (int i = 0; i < threads.size(); i++) {
             threads.get(i).join();
        }
         long end = System.nanoTime();
         log.info("the left account is: {}", account);
         log.info("total time is: {}ms", (end - begin) / (1000 * 1000));
    }
 }
 ​
 public class CasDemo {
     public static void main(String[] args) throws InterruptedException {
         BankAccount bankAccount = new BankAccount(10000);
         bankAccount.concurrentWithdraw(10);
    }
 }

进一步使用AtomicInteger的自增自减方法简化

 public void withdraw(int amount) {
         account.addAndGet(-1 * amount);
    }
赞(1) 打赏
未经允许不得转载:Stephen Young » CAS原理及应用
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏