本文导读:

  • 前言
  • 如何保障线程安全
  • CAS原理剖析
  • CPU如何保证原子操作
  • 解密CAS底层指令
  • 小结

朋友,文章优先发布在公众号上,如果你愿意,可以扫右侧二维码支持一下下~,谢谢!

前言

日常编码过程中,基本不会直接用到 CAS 操作,都是通过一些JDK 封装好的并发工具类来使用的,在 java.util.concurrent 包下。

但是面试时 CAS 还是个高频考点,所以呀,你还不得不硬着头皮去死磕一下这块的技能点,总比一问三不知强吧?

一般都是先针对一些简单的并发知识问起,还有的面试官,比较直接:

面试官:Java并发工具类中的 CAS 机制讲一讲?

小东:额?大脑中问自己「啥是 CAS?」我听过的,容我想一想...

一分钟过去了...

小东:嘿嘿~,这块我看过的,记不大清楚了。

面试官:好的,今天先到这吧~

小东:在路上

biaoqing

当然 CAS 你若真不懂,你可以引导面试官到你擅长的技术点上,用你的其他技能亮点扳回一局。

接下来,我们通过一个示例代码来说:

// 类的成员变量 static int data = 0; // main方法内代码 IntStream.range(0, 2).forEach((i) -> {         new Thread(() -> {                 try {                         Thread.sleep(20);                 } catch (InterruptedException e) {                         e.printStackTrace();                 }                 IntStream.range(0, 100).forEach(y -> {                         data++;                 });         }).start(); });      try {             Thread.sleep(2000);     } catch (InterruptedException e) {             e.printStackTrace();     }  System.out.println(data); }

结合图示理解:

线程不安全

上述代码,问题很明显,data 是类中的成员变量,int 类型,即共享的资源。当多个线程同时
执行 data++ 操作时,结果可能不等于 200,为了模拟出效果,线程中 sleep 了 20 毫秒,让线程就绪,代码运行多次,结果都不是 200 。

如何保障线程安全

示例代码执行结果表明了,多个线程同时操作共享变量导致了结果不准确,线程是不安全的。如何解决呢?

方案一:使用 synchronized 关键字

使用 synchronized 关键字,线程内使用同步代码块,由JVM自身的机制来保障线程的安全性。

synchronized 关键代码:

// 类中定义的Object锁对象 Object lock = new Object();    // synchronized 同步块 () 中使用 lock 对象锁定资源 IntStream.range(0, 100).forEach(y -> {         synchronized (lock.getClass()) {                 data++;         } });

synchronized保障线程安全

方案二:使用 Lock 锁

高并发场景下,使用 Lock 锁要比使用 synchronized 关键字,在性能上得到极大的提高。
因为 Lock 底层是通过 AQS + CAS 机制来实现的。关于 AQS 机制可以参见往期文章 <<通过通过一个生活中的案例场景,揭开并发包底层AQS的神秘面纱>> 。CAS 机制会在文章中下面讲到。

使用 Lock 的关键代码:

// 类中定义成员变量   Lock lock = new ReentrantLock();  // 执行 lock() 方法加锁,执行 unlock() 方法解锁 IntStream.range(0, 100).forEach(y -> {         lock.lock();         data++;         lock.unlock(); });

结合图示理解:

Lock锁保障线程安全

方案三:使用 Atomic 原子类

除上面两种方案还有没有更为优雅的方案?synchronized 的使用在 JDK1.6 版本以后做了很多优化,如果并发量不大,相比 Lock 更为安全,性能也能接受,因其得益于 JVM 底层机制来保障,自动释放锁,无需硬编码方式释放锁。而使用 Lock 方式,一旦 unlock() 方法使用不规范,可能导致死锁。