每日一问:谈谈 synchronized 和 CAS 机制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每日一问:谈谈 synchronized 和 CAS 机制相关的知识,希望对你有一定的参考价值。

参考技术A

昨天的文章 我们针对 Java 语言的 "happends-before" 原则做了一个非常简单的表述,以致于有同学提到我这个话语的严谨性问题。而这个原则在 Java 语言里面非常重要,以致于我必须重新引用一下相关书籍的话来进行论述。

"happends-before" 先行发生原则是 Java 内存模型中定义的两项操作之间的偏序关系,如果说操作 A 先行发生于操作 B,那么操作 A 产生的影响一定应该被操作 B 所观察到。

而对于我们 volatile 保证的可见性, synchronized 和 final 关键字也同样可以做到。那我们今天就来简单讲一下我们非常常用的 synchronized 和似乎在 android 中少有听到的 CAS 机制。

synchronized 采用的是 CPU 悲观锁机制,即线程获得的是独占锁。独占锁就意味着 其他线程只能依靠阻塞来等待线程释放锁 。而在 CPU 转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起 CPU 频繁的上下文切换导致效率很低。尽管 Java1.6 为 synchronized 做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。

CAS 是英文单词 Compare And Swap 的缩写,翻译过来就是比较并替换。它当中使用了3个基本操作数:内存地址 V,旧的预期值 A,要修改的新值 B。采用的是一种乐观锁的机制,它不会阻塞任何线程,所以在效率上,它会比 synchronized 要高。所谓乐观锁就是: 每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

所以,在并发量非常高的情况下,我们尽量的用同步锁,而在其他情况下,我们可以灵活的采用 CAS 机制。

在 java.util.concurrent.atomic 包下,一系列以 Atomic 开头的包装类。例如 AtomicBoolean , AtomicInteger , AtomicLong 等,它们就是典型的利用 CAS 机制实现的原子操作类。

此外, Lock 系列类的底层实现以及 Java 1.6 在 synchronized 转换为重量级锁之前,也会采用到 CAS 机制。

关于 CAS 机制的更多相关信息请移步:
漫画:什么是CAS机制?(进阶篇)

Java -- 每日一问:synchronized和ReentrantLock有什么区别呢?


Java

典型回答

synchronized 是 Java 内建的同步机制,所以也有人称其为 Intrinsic Locking,它提供了互斥的语义和可见性,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在那里。

在 Java 5 以前,synchronized 是仅有的同步手段,在代码中, synchronized 可以用来修饰方法,也可以使用在特定的代码块儿上,本质上 synchronized 方法等同于把方法全部语句用 synchronized 块包起来。

ReentrantLock,通常翻译为再入锁,是 Java 5 提供的锁实现,它的语义和 synchronized 基本相同。再入锁通过代码直接调用 lock() 方法获取,代码书写也更加灵活。与此同时,ReentrantLock 提供了很多实用的方法,能够实现很多 synchronized 无法做到的细节控制,比如可以控制 fairness,也就是公平性,或者利用定义条件等。但是,编码中也需要注意,必须要明确调用 unlock() 方法释放,不然就会一直持有该锁。

synchronized 和 ReentrantLock 的性能不能一概而论,早期版本 synchronized 在很多场景下性能相差较大,在后续版本进行了较多改进,在低竞争场景中表现可能优于 ReentrantLock。

高手回答

ReentrantLock是Lock的实现类,是一个互斥的同步器,在多线程高竞争条件下,ReentrantLock比synchronized有更加优异的性能表现。

1 用法比较
Lock使用起来比较灵活,但是必须有释放锁的配合动作
Lock必须手动获取与释放锁,而synchronized不需要手动释放和开启锁
Lock只适用于代码块锁,而synchronized可用于修饰方法、代码块等

2 特性比较
ReentrantLock的优势体现在:
具备尝试非阻塞地获取锁的特性:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
能被中断地获取锁的特性:与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
超时获取锁的特性:在指定的时间范围内获取锁;如果截止时间到了仍然无法获取锁,则返回

3 注意事项
在使用ReentrantLock类的时,一定要注意三点:
在finally中释放锁,目的是保证在获取锁之后,最终能够被释放
不要将获取锁的过程写在try块内,因为如果在获取锁时发生了异常,异常抛出的同时,也会导致锁无故被释放。
ReentrantLock提供了一个newCondition的方法,以便用户在同一锁的情况下可以根据不同的情况执行等待或唤醒的动作。

你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。


以上是关于每日一问:谈谈 synchronized 和 CAS 机制的主要内容,如果未能解决你的问题,请参考以下文章

每日一问:谈谈 SharedPreferences 的 apply() 和 commit()

Java -- 每日一问:synchronized和ReentrantLock有什么区别呢?

Java -- 每日一问:谈谈接口和抽象类有什么区别?

Java -- 每日一问:谈谈你知道的设计模式?

Java -- 每日一问:谈谈 Spring Bean 的生命周期和作用域?

Java -- 每日一问:谈谈你对 Java 平台的理解?