synchronized面试相关

synchronized锁升级一个过程

在这里插入图片描述

 

synchronized出现锁升级的原因
在jdk1.6以前,synchronized锁是低效的,因为synchronized在当初来讲,一直是一个重量级的锁,只允许获取锁的线程执行,而其它线程都要阻塞。等待线程释放锁之后,再由cpu进行线程调度。这种由用户态到内核态的切换,是会涉及到上下文切换,要保存环境资源,是十分消耗性能的。所以在1.6之前synchronized同步的方法,执行效率都很慢。于是设计者在jdk1.6提出了锁升级。将synchronized改出了四种状态。让synchronized一步步升级到重量级锁,而不是一上来就是重量级锁。

锁升级的状态
在升级后,锁的状态有四种,从低到高分别是 无锁 偏向锁 轻量级锁 重量级锁,该过程是不可逆的,即不存在锁降级。

无锁
先来单独讲一下无锁,无锁指的就是一份资源,未被锁定的时候的状态。该状态下,多个线程都可以访问它,多个线程会自己默默进行循环CAS判断,即同一时间只允许一个线程完成对这份资源的修改。

当一个线程想要对这份资源上锁时,就会发生锁升级,具体是通过修改对象头Mark work里的标志位。

偏向锁
偏向锁,指的是一个线程访问同步代码块的时候,锁由无锁状态升级到偏向锁。一个线程完成该同步代码块后,会判断是否还是该线程持有锁,如果是则继续向下执行会将由该线程一直持有该偏向锁。

在偏向锁状态下,只会在切换ThreadId的时候进行一次CAS操作,之后就是去查看mark work里面的标志位信息是否是偏向锁,如果是则继续执行。

偏向锁只有在遇到其它线程抢夺的时候(自己又刚好空闲),才会释放锁,自己是不会主动释放锁的。

关于偏向锁的撤销,,需要等待全局安全点,即在某个时间点上没有字节码正在执行时,它会先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态。

什么是锁竞争
上述偏向锁中的多个线程争抢的时候发生的线程置换,并没有发生锁竞争。

这里要明确一下什么是锁竞争:如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。

发生锁竞争后,就会升级到轻量级锁。

轻量级锁
当多个线程竞争锁的时候,锁就会升级成为轻量级锁。在轻量级锁的状态下,只有一个线程能够抢占到锁,其它线程并不会进入阻塞状态,而是处于自旋状态,进行循环cas判断,是否可以获取锁资源。这样的设定,通过增加cpu处理时间,来换取避免用户态和内核态切换而带来的性能损失。

重量级锁
当一个线程在上面的自旋取锁操作中,执行次数过多,那么该锁就会升级成为重量级锁。该状态下,只允许一个线程持有锁,其它线程进入阻塞状态,等待cpu的重新调度。
 

synchronized和ReentrantLock两个同步锁的区别

相似点:这两个同步方式有很多相似之处,他们都是加锁方式同步,而且都是阻塞式同步,也就是说当一个线程获取对象锁之后,进入同步块,其他访问该同步块的线程都必须阻塞在该同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态和内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)。

功能区别:这两种方式最大的区别就是对于synchronized来说,它是Java语言关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock他是jdk1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句来完成

便利性:很明显Synchronized的使用方便简洁,并且由编译器去保证锁的加锁和释放锁,而ReentrantLock则需要手动声明加锁和释放锁的方法,为了避免忘记手动释放锁,最好是在finally中声明释放锁。

锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized

性能区别:在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

おすすめ

転載: blog.csdn.net/cpcpcp123/article/details/121832269