【⾯试】Synchronized常见⾯试题
问题⼤纲
⼀、为什么有Synchronized?
1、为什么需要 synchronized?什么场景下使⽤ synchronized?
多个线程操作共享数据时,加锁保证访问共享数据的线程安全性。
⼆、Synchronized是什么?
1、 synchronized 这三种加锁⽅式(作⽤对象:静态⽅法、⾮静态⽅法、代码块)作⽤范围的区别?(*4)
先明确⼀点:锁是加在对象上⾯的,重要事情再说⼀遍:在对象上加锁(这也是为什么 wait / notify 需要在锁定对象后执⾏,只有先拿到锁才能释放锁)
这三种作⽤范围的区别实际是被加锁的对象的区别,请看下表:
作⽤范围锁对象
⾮静态⽅法当前对象 => this
静态⽅法类对象 => SynchronizedSample.class (⼀切皆对象,这个是类对象)
代码块指定对象 => lock (以上⾯的代码为例)
2、Synchronized修饰的⽅法在抛出异常时,会释放锁吗?
会,代码执⾏完毕或者异常结束会释放锁。
试图获取锁的时候不能设定超时,不能中断⼀个正在使⽤锁的线程,相对⽽⾔,Lock可以中断和设置超时
3、synchronized 是公平锁还是⾮公平锁?
⾮公平,新来的线程有可能⽴即获得监视器,⽽在等待区中等候已久的线程可能再次等待,不过这种抢占的⽅式可以预防饥饿。
三、Synchronized怎么实现的?
三.synchronized关键字,是怎么保证线程安全的呢?
内存结构;原⼦性、 有序性、可见性;锁类型、切换流程、优缺点。
1、synchronized底层源码如何实现?(*4)
synchronized 在代码块上是通过 monitorenter 和 monitorexit 指令实现,在静态⽅法和 ⽅法上加锁是在⽅法的 flags 中加⼊
ACC_SYNCHRONIZED 。
2、synchronized本质上是通过什么保证线程安全的? 分三个⽅⾯回答:加锁和释放锁的原理,保证可见性原理。
1、原⼦性
互斥锁:monitorenter 和 monitorexit 、ACC_SYNCHRONIZED,保证同⼀时刻只有⼀个线程执⾏。
2、有序性
通过Acquire屏障和Relea屏障保证代码块内部可以重排,但是代码块内部和代码块外部的指令是不能重排。
3、可见性
进⼊synchronized代码块时,将⽤到的变量从该线程的⼯作内存中清除,转⽽从主内存中获取。退出synchronized代码块时,会将代码块内⽤到的变量的修改,刷新到主内存中。
追问1:为什么monitorexit指令出现了两次?
第1次为同步正常退出释放锁;第2次为发⽣异步退出释放锁。
追问2:可重⼊原理?
synchronized底层的实现原理是利⽤计算机系统的mutex Lock实现。每⼀个可重⼊锁都会关联⼀个线程ID和⼀个锁状态status。
湿疹有传染性吗当⼀个线程请求⽅法时,会去检查锁状态,如果锁状态是0,代表该锁没有被占⽤,直接进⾏CAS操作获取锁,将线程ID替换成⾃⼰的线程ID。如果锁状态不是0,代表有线程在访问该⽅法。此时,如果线程ID是⾃⼰的线程ID,如果是可重⼊锁,会将status⾃增1,然后获取到该锁,进⽽执⾏相应的⽅法。如果是⾮重⼊锁,就会进⼊阻塞队列等待。
释放锁时,可重⼊锁,每⼀次退出⽅法,就会将status减1,直⾄status的值为0,最后释放该锁。释放锁时,⾮可重⼊锁,线程退出⽅法,直接就会释放该锁。
所以,从⼀定程度上来说,可重⼊锁可以避免死锁的发⽣。
追问3:在并发时,程序的执⾏可能会出现乱序,直观感觉前⾯的代码,会在后⾯执⾏,为什么?
这其实和as-if-rial语义有关。as-if-rial语义指编译器和处理器⽆论如何优化(提⾼并⾏度),不管怎么重排序,单线程执⾏结果都不能被改变。
由于synchronized修饰的代码,同⼀时间只能被同⼀线程访问。那么可以认为是单线程执⾏的。所以可以保证其有序性。但注意synchronized虽然能保证有序性,但⽆法禁⽌指令重排和处理器优化。
3、那你分别跟我讲讲 JDK 6 以前 synchronized 为什么这么重?
在 Java 6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进⾏⽤户态到内核态的切换,所以同步操作是⼀个⽆差别的重量级操作。
4、JDK 6 之后 synchronized 做了优化,跟我讲讲?什么是JVM⾥的偏斜锁、轻量级锁、重量级锁?(*16)
JVM 在 JDK1.6 中引⼊了分级锁机制来优化 Synchronized,当⼀个线程获取锁时:
S1:⾸先对象锁将成为⼀个偏向锁,这样做是为了优化同⼀线程重复获取导致的⽤户态与内核态的切换问题;
S2:其次如果有多个线程竞争锁资源,锁将会升级为轻量级锁,它适⽤于在短时间内持有锁,且分锁有交替切换的场景;
S3:轻量级锁还使⽤了⾃旋锁来避免线程⽤户态与内核态的频繁切换,⼤⼤地提⾼了系统性能;
S4:但如果锁竞争太激烈了,那么同步锁将会升级为重量级锁。
追问1:你知道“⾃旋锁”是做什么的吗?它的使⽤场景是什么?
⾃旋锁是尝试获取锁的线程不会⽴即阻塞,采⽤循环的⽅式去获取锁,好处是减少了上下⽂切换,缺点是消耗cpu,适⽤于锁竞争不激烈,且占⽤锁时间⾮常短的情况。
⾃旋锁:竞争锁的失败的线程,并不会真实的在操作系统层⾯挂起等待,⽽是JVM会让线程做⼏个空循环(基于预测在不久的将来就能获得),在经过若⼲次循环后,如果可以获得锁,那么进⼊临界区,如果还不能获得锁,才会真实的将线程在操作系统层⾯进⾏挂起。
适⽤场景:⾃旋锁可以减少线程的阻塞,这对于锁竞争不激烈,且占⽤锁时间⾮常短的代码块来说,有较⼤的性能提升,因为⾃旋的消耗会⼩于线程阻塞挂起操作的消耗。
如果锁的竞争激烈,或者持有锁的线程需要长时间占⽤锁执⾏同步块,就不适合使⽤⾃旋锁了,因为⾃旋锁在获取锁前⼀直都是占⽤cpu做⽆⽤功,线程⾃旋的消耗⼤于线程阻塞挂起操作的消耗,造成cpu的浪费。祁同伟扮演者
5、什么是锁的升级、降级?【第16讲】
多个线程等待同⼀个snchronized锁的时候,JVM如何选择下⼀个获取锁的线程?
所谓锁的升级、降级,就是 JVM 优化 synchronized 运⾏的机制,当 JVM 检测到不同的竞争状况时,会⾃动切换到适合的锁实现,这种切换就是锁的升级、降级。
当没有竞争出现时,默认会使⽤偏斜锁。JVM 会利⽤ CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表⽰这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应⽤场景中,⼤部分对象⽣命周期中最多会被⼀个线程锁定,使⽤偏斜锁可以降低⽆竞争开销。
如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使⽤普通的轻量级锁;否则,进⼀步升级为重量级锁。
合同价
傣族我注意到有的观点认为 Java 不会进⾏锁降级。实际上据我所知,锁降级确实是会发⽣的,当 JVM 进⼊安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进⾏降级。
四、Synchronized使⽤?
1、如何提⾼Synchronized并发性能?
类似:Synchronized使得同时只有⼀个线程可以执⾏,性能⽐较差,有什么提升的⽅法?
减少锁竞争,是优化 Synchronized 同步锁的关键。我们应该尽量使 Synchronized 同步锁处于轻量级锁或偏向锁,这样才能提⾼Synchronized 同步锁的性能;通过减⼩锁粒度来降低锁竞争也是⼀种最常⽤的优化⽅法;另外我们还可以通过减少锁的持有时间来提⾼Synchronized 同步锁在⾃旋时获取锁资源的成功率,避免 Synchronized 同步锁升级为重量级锁。
2、使⽤Synchronized关键字需要注意什么?(*4)
1. 锁对象不能为空。
锁对象的信息是保留在对象头中的,如果对象为空,则锁的信息也就不存在了。
2. 作⽤域不宜过⼤
乡愁歌词
如果把过多的代码放在其中,程序的运⾏会变为串⾏,速度会下降。把那些影响线程安全的代码串⾏执⾏;不需要线程安全代码并⾏执⾏,达到效率最⾼。
3. 避免死锁
避免让线程对锁持有并等待的情况出现。
五、Synchronized延申?
1、 Synchronized由什么样的缺陷?
类似:
Java 中除了 synchronized 还有别的锁吗?
想更加灵活地控制锁的释放和获取(现在释放锁和获取锁的时机都被规定死了),怎么办?
Java 使⽤Synchronized关键字有什么隐患?
1、不能设置锁超时时间;
2、不能通过代码释放锁;
3、容易造成死锁。
2、Java Lock怎么弥补这些缺陷的?
还有 ReentrantLock 也可以实现加锁。
追问1:Synchronized和lock的区别(*3)
类似:Synchronized和Lock的对⽐、区别、选择?
锁类型synchronized lock
性能资源竞争不激烈时,synchronize性能更好。资源竞争激烈时,lock性能更好
吕后锁机制在JVM层⾯实现,系统会监控锁的释放与否是JDK代码实现,需要⼿动释放资源
编程synchronized更简洁,可以⽤在⽅法/代码块lock功能多,更灵活,缺点是只能写在代码⾥,不能直接修改代码
延伸:synchronize会根据锁竞争情况,从偏向锁-->轻量级锁-->重量级锁升级,lock可以采⽤⾮阻塞的⽅式获取锁。
3、synchronized和ReentrantLock有什么区别呢?【第15讲】(*3)
【1】相同:两者都是可重⼊锁,都提供互斥语义,当⼀个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在那⾥。【2】区别
synchronized 依赖于 JVM,⽽ ReentrantLock 依赖于 API。
ReentrantLock 增加了三点功能:等待可中断、可实现公平锁、可实现选择性通知(锁可以绑定多个条件)。
【3】性能:不能⼀概⽽论,synchronized早期性能相差较⼤,后续版本有较多改进,在低竞争场景中表现可能优于 ReentrantLock。
补充:
0、Lock接⼝⽐synchronized锁定操作更⼴泛、更灵活。可以具有完全不同的属性,并且可以⽀持多个关联的Condition条件对象。
1、ReentrantLock锁⽐synchronized更具扩展性,灵活⾼效,额外具有嗅探锁定、多路分⽀通知等功能;
2、Lock接⼝提供⽐synchronized更⼴泛与灵活的操作;
3、Condition给ReentrantLock提供了等待/通知,⽽且相⽐synchronized中函数(wait,notify,notifyAll)更加灵活性,还可以实现多路通知功能,
四、参考
原理
1、
2、
3、
4、
同步
1、
2、
⾯试
1、
土豆炖牛肉做法2、
兵变3、
原⼦性、有序性、可见性
1、
2、
3、
可重⼊性
1、
2、