【并发编程面试题】Synchronized八连问

导读:本篇文章讲解 【并发编程面试题】Synchronized八连问,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1. Synchronized用过吗 , 其原理是什么 ?

Synchronized 是JVM提供的一种互斥同步的实现方式,查看Synchronized修饰的程序块编译后的字节码会发现编译前后生成了monitorenter和monitexit两个字节码指令。

在虚拟机执行monitorenter指令的时候,首先会尝试获取对象锁,如果获取对象锁成功或者当前线程已经获取了该对象锁,那么,锁的计数器就会加一。如果获取对象锁失败,当前线程就要阻塞等待,知道对象锁被另外一个线程释放为止。

在虚拟机指定monitorexit指令的时候,锁的计数器就会减一,当锁的计数器为0的时候,就会释放掉锁。


2. 这个对象锁到底是什么?如何确定对象锁?

锁的本质其实就是monitorenter和monitorexit字节码指令的一个Reference参数,也就是解锁和加锁的对象。

  • 如果Synchronized 明确指定了锁对象,如Synchronized (变量名)、Synchronized (this),那么加解锁对象就是该对象
  • 如果Synchronized 修饰的方法为非静态方法,那么锁对象为该方法对象的对象
  • 如果Synchronized 修饰的方法为静态方法,那么锁对象为该方法对应的类对象。

3. 什么是可重入性 , 为什么说Synchronized是可重入锁?

可重入锁是指同一线程再外层方法获取锁的时候,再次进入该线程内层会自动获取锁(前提,锁的对象是同一个锁),不会因为之前已经获取过还没有释放而阻塞。

可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况。

对于Synchronized而言,当执行monitorenter字节码指令的时候,如果这个对象没有锁定或者该线程已经拥有该对象锁,锁的计数器会加一,其实本质上就是通过这种方式实现了可重入性。


4. JVM对Java的原生锁做了哪些优化?

在Java6之前,Monitor的实现完全依赖于操作系统的互斥锁来实现。

由于Java层面的线程与操作系统的原生线程有映射关系,如果将一个线程进行阻塞或唤醒需要操作系统的协助,这就需要从用户态切换成内核态来执行,但是这种切换代价十分昂贵,很耗处理器事件,现代JDK对此做了大量的优化。

其中的一种优化就是使用自旋锁,也就是线程在进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁,这时就无需再让线程进行阻塞操作,避免了从用户态到内核态的切换。

并且现代JDK还提供了三种不同的Monitor实现,也就是三种锁,分别是偏向锁、轻量级锁、重量级锁。

这三种锁使得JDK得以优化Synchronized的运行,当JVM检测到不同的竞争状况时,会自动切换到合适的锁实现,这就是锁的升级和降级。

如果没有出现竞争关系,默认会使用偏向锁。

当线程A第一次竞争到锁时,通过操作修改MarkWord钟偏向线程ID、偏向模式。

如果不存在其他线程竞争的画,那么持有偏向锁的线程将永远不再需要同步

如果另一线程试图锁定某个被偏斜过的对象,JVM就会撤销偏斜锁切换到轻量级锁的实现。

而轻量级锁依赖CAS操作MarkWord来试图获取锁,如果重试成功,就会使用普通的轻量级锁,否则会进一步升级成重量级锁

【JUC】10. synchronized与锁升级_起名方面没有灵感的博客-CSDN博客


5. 为什么说Synchronized是非公平锁?

Synchronized的非公平性主要表现在获取锁的行为上,获取到锁并未是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做是为提高执行性能,但是缺点就是可能会产生线程饥饿现象。


6. 什么是锁消除和锁粗化?

  • 锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。
  • 锁粗化是指一系列的连续操作都对同一个对象进行反复加锁和解锁,甚至加锁操作在循环体内,频繁地进行互斥同步操作也会导致不必要地性能损耗

7. 为什么说Synchronized是一个悲观锁?乐观锁的实现原理又是什么?什么是CAS,它有什么特性?

Synchronized显然是一个悲观锁,因为它的并发策略就是悲观的。无论是否产生竞争,任何的数据操作都必须进行加锁、用户态核心转换、维护锁计数器和检查是否有阻塞的线程需要被唤醒的操作。

乐观锁的并发策略是先进行操作,如果没有其他线程征用数据,那么操作就成功了。这种并发策略的许多实现不需要线程挂起,所以也成为非阻塞同步。

乐观锁的核心算法是CAS,它涉及到三个操作数:内存值、预期值、新值。当且仅当预期值和内存值相等时才将内存值修改为新值。

这里的处理逻辑就是,首先检查某块内存的值是否跟之前读取的一样,如果不一样标识期间此内存已经被别的线程更改过了,舍弃本次操作,否则说明期间没有其他线程对此内存操作,可以把新值设置给此块内存。

CAS具有原子性,它的原子性是由CPU硬件指令是实现保证的。


8. 乐观锁一定就是好的吗?

乐观锁虽然避免了悲观锁独占对象的现象,同时也提高了并发性能,但是也存在一定的缺点

  • 乐观锁只能保证一个共享变量的原子操作,如果多一个或几个变量,乐观锁就会变得力不从心,但互斥锁能轻易解决,不管对象数量多少对象颗粒度大小
  • 长时间自旋可能会导致开销大,假如CAS长时间不成功而一直自旋,会给CPU带来很大开销。
  • ABA问题。CAS的核心思想是想通过对比内存预值是否一样判断内存值是否被改过,但是这种判断逻辑并不严谨。比如内存值原来是A,被另外一个线程修改为B,最后又修改成A了。这样CAS就会认为内存值没有发生改变,实际上是有被其他线程改过的。 解决的思路就是引入版本号,每次变量更改就把版本号+1.

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94947.html

(1)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!