sychrongized 关键字

问题大纲
一、为什么有Synchronized?
1、为什么需要 synchronized?什么场景下使用 synchronized?
二、Synchronized是什么?
1、 synchronized 这三种加锁方式(作用对象:静态方法、非静态方法、代码块)作用范围的区别?(*4)
2、Synchronized修饰的方法在抛出异常时,会释放锁吗?
3、synchronized 是公平锁还是非公平锁?
三、Synchronized怎么实现的?
1、synchronized底层源码如何实现?(*4)
2、synchronized本质上是通过什么保证线程安全的? 分三个方面回答:加锁和释放锁的原理,保证可见性原理。
追问1:为什么monitorexit指令出现了两次?
追问2:可重入原理?
追问3:在并发时,程序的执行可能会出现乱序,直观感觉前面的代码,会在后面执行,为什么?
3、那你分别跟我讲讲 JDK 6 以前 synchronized 为什么这么重?
4、JDK 6 之后 synchronized 做了优化,跟我讲讲? 什么是JVM里的偏斜锁、轻量级锁、重量级锁?(*16)
追问1:你知道“自旋锁”是做什么的吗?它的使用场景是什么?
5、什么是锁的升级、降级?【第16讲】
四、Synchronized使用?
1、如何提高Synchronized并发性能?
2、使用Synchronized关键字需要注意什么?(*4)
五、Synchronized延申?
1、 Synchronized由什么样的缺陷?
2、Java Lock怎么弥补这些缺陷的?
追问1:Synchronized和lock的区别(*3)
3、synchronized和ReentrantLock有什么区别呢?【第15讲】(*3)
四、参考
一、为什么有Synchronized?
1、为什么需要 synchronized?什么场景下使用 synchronized?
多个线程操作共享数据时,加锁保证访问共享数据的线程安全性。

二、Synchronized是什么?
1、 synchronized 这三种加锁方式(作用对象:静态方法、非静态方法、代码块)作用范围的区别?(*4)
先明确一点:锁是加在对象上面的,重要事情再说一遍:在对象上加锁(这也是为什么 wait / notify 需要在锁定对象后执行,只有先拿到锁才能释放锁)

这三种作用范围的区别实际是被加锁的对象的区别,请看下表:

作用范围    锁对象
非静态方法    当前对象 => this
静态方法    类对象 => SynchronizedSample.class (一切皆对象,这个是类对象)
代码块    指定对象 => lock (以上面的代码为例)
2、Synchronized修饰的方法在抛出异常时,会释放锁吗?
会,代码执行完毕或者异常结束会释放锁。

试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时
synchronized 是公平锁还是非公平锁?
非公平,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,不过这种抢占的方式可以预防饥饿。


三.synchronized关键字,是怎么保证线程安全的呢?
内存结构;原子性、 有序性、可见性;锁类型、切换流程、优缺点。

1、synchronized底层源码如何实现?(*4
synchronized 在代码块上是通过 monitorenter 和 monitorexit 指令实现,在静态方法和 方法上加锁是在方法的 flags 中加入 ACC_SYNCHRONIZED 。

2、synchronized本质上是通过什么保证线程安全的? 分三个方面回答:加锁和释放锁的原理,保证可见性原理。
1、原子性
互斥锁:monitorenter 和 monitorexit 、ACC_SYNCHRONIZED,保证同一时刻只有一个线程执行。

2、有序性
通过Acquire屏障和Release屏障保证代码块内部可以重排,但是代码块内部和代码块外部的指令是不能重排。

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-serial语义有关。as-if-serial语义指编译器和处理器无论如何优化(提高并行度),不管怎么重排序,单线程执行结果都不能被改变。
由于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)
锁对象不能为空。
锁对象的信息是保留在对象头中的,如果对象为空,则锁的信息也就不存在了。

作用域不宜过大
如果把过多的代码放在其中,程序的运行会变为串行,速度会下降。把那些影响线程安全的代码串行执行;不需要线程安全代码并行执行,达到效率最高。

避免死锁
避免让线程对锁持有并等待的情况出现。

五、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可以采用非阻塞的方式获取锁。
1
3、synchronized和ReentrantLock有什么区别呢?【第15讲】(*3)
【1】相同:两者都是可重入锁,都提供互斥语义,当一个线程已经获取当前锁时,其他试图获取的线程只能等待或者阻塞在那里。

【2】区别

synchronized 依赖于 JVM,而 ReentrantLock 依赖于 API。
ReentrantLock 增加了三点功能:等待可中断、可实现公平锁、可实现选择性通知(锁可以绑定多个条件)。
【3】性能:不能一概而论,synchronized早期性能相差较大,后续版本有较多改进,在低竞争场景中表现可能优于 ReentrantLock。
补充:
1、Lock接口比synchronized锁定操作更广泛、更灵活。可以具有完全不同的属性,并且可以支持多个关联的Condition条件对象。
2、ReentrantLock锁比synchronized更具扩展性,灵活高效,额外具有嗅探锁定、多路分支通知等功能;
3、Lock接口提供比synchronized更广泛与灵活的操作;
4、Condition给ReentrantLock提供了等待/通知,而且相比synchronized中函数(wait,notify,notifyAll)更加灵活性,还可以实现多路通知功能,
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值