互联网JAVA面试常问问题(五)

  • 2019 年 10 月 6 日
  • 筆記

关于JAVA锁,还有以下我们需要知道的。

Synchronized中的CAS

面试题(四)中,我们对Synchronized应该有所印象了,它最大的特征就是在同一时刻只有一个线程能够获得对象的监视器(monitor),从而进入到同步代码块或者同步方法之中,即表现为互斥性(排它性)。

JAVA1.6之前,在没有引入轻量级锁和偏向锁之前,Synchronized最主要的问题是:在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。

此时引入CAS,它并不是简单的将线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。

1 什么是CAS?

CAS的全称是Compare And Swap,即比较交换,其算法核心思想如下

执行函数:CAS(V,E,N)

其包含3个参数

V表示要更新的变量  E表示预期值  N表示新值

使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,所以当前线程获取到锁的时候同时也会阻塞其他线程获取该锁。

而CAS操作(又称为无锁操作)是一种乐观锁策略,它假设所有线程访问共享资源的时候不会出现冲突,,即使出现冲突就重试当前操作直到没有冲突为止。

2 CAS的应用场景

在J.U.C包中利用CAS实现类有很多,可以说是支撑起整个concurrency包的实现,在Lock实现中会有CAS改变state变量,在atomic包中的实现类也几乎都是用CAS实现,以后会详细分别介绍。

3 CAS的问题

ABA问题

因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以沿袭数据库中常用的乐观锁方式,添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3C。java这么优秀的语言,当然在java 1.5后的atomic包中提供了AtomicStampedReference来解决ABA问题,解决思路就是这样的。

自旋时间过长

使用CAS时非阻塞同步,也就是说不会将线程挂起,会自旋(无非就是一个死循环)进行下一次尝试,如果这里自旋时间过长对性能是很大的消耗。

在文章结尾,给大家提出这样一个问题,假设现在开启10个线程,每个线程都累加了100000次,如果想得到10*100000=1000000这个结果,如下代码有什么问题呢?欢迎大家后台留言和小强一起讨论。

    public class SynchronizedTest implements Runnable {      private static int count = 0;        public static void main(String[] args) {      for (int i = 0; i < 10; i++) {             Thread thread = new Thread(new SynchronizedTest());             thread.start();         }          try {             Thread.sleep(500);         } catch (InterruptedException e) {         }         System.out.println("expected 1000000 but result is: " + count);     }   @Override      public void run() {  ‍      ‍‍for (int i = 0; i < 100000; i++)           count++;     }  }