Synchronized关键字
Synchronized实际上就是通过操作Java对象的对象监视器(内部锁),来实现锁的操作
进入同步块时,线程必须先获得与对象关联的监视器。
- 如果监视器未被其他线程占用,线程将成功获取监视器,并进入同步块执行。
- 如果监视器已被其他线程占用,线程将被阻塞,直到监视器被释放。
退出同步块时,线程释放与对象关联的监视器。
- 如果有其他线程在等待获取该监视器,其中之一将被唤醒,获得监视器,然后执行相应的同步块。
那么它是怎么实现的呢,在底层编译后的jvm指令中,我们会发现
1 | 3: monitorenter // 获取Synchronized锁,进入同步代码块 |
或者
1 | synchronized void sayHelloHi(); |
来实现锁的操作
那么,对象监视器操作的是对象实例的对象头,来实现对对象打上锁的标记
对象头内容
Synchronized底层原理-Linux管程模型
synchronized基于管程模型。同步共享变量和入口等待队列来实现互斥,通过条件变量与条件变量等待队列实现通信。多线程竞争时,拿不到对象头monitor锁的线程便会被放到入口等待队列中。而获取到对象头monitor锁的线程则会进入临界区。此时同步代码块中存在条件变量判断是否进入wait方法时,满足条件变量则会继续执行,不满足条件变量的线程会被封装成ObjectWaiter放入条件等待队列中。待满足条件遍历的线程执行完毕时,会选择一个条件变量等待队列中的线程出队,唤醒线程进入临界区,与其他临界区的线程(入口等待队列)竞争
管程:进程间互斥(只有一个进程持有锁)使用管程
JVM对Synchronized的优化
锁膨胀
锁膨胀升级的方向:无锁 –> 偏向锁 –> 轻量级锁 –> 重量级锁
偏向锁
一句话总结它的作用:减少统一线程获取锁的代价。在大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得,那么此时就是偏向锁。
核心思想:
如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word
的结构也就变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word
的锁标记位为偏向锁以及当前线程ID等于Mark Word
的ThreadID即可,这样就省去了大量有关锁申请的操作。
轻量级锁
轻量级锁是由偏向锁升级而来,当存在第二个线程申请同一个锁对象时,偏向锁就会立即升级为轻量级锁。
假如此时持有线程的锁没有释放,第二个线程便会在自旋等待(自旋锁)
轻量级锁向重量级锁升级过程
若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
重量级锁
重量级锁是由轻量级锁升级而来,当同一时间有多个线程竞争锁时,锁就会被升级成重量级锁,此时其申请锁带来的开销也就变大。
重量级锁一般使用场景会在追求吞吐量,同步块或者同步方法执行时间较长的场景。
自旋锁和自适应自旋锁
轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。
自旋锁:许多情况下,共享数据的锁定状态持续时间较短,切换线程不值得,通过让线程执行循环等待锁的释放,不让出CPU。如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式。但是它也存在缺点:如果锁被其他线程长时间占用,一直不释放CPU,会带来许多的性能开销。
自适应自旋锁:这种相当于是对上面自旋锁优化方式的进一步优化,它的自旋的次数不再固定,其自旋的次数由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定,这就解决了自旋锁带来的缺点。
偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。
synchronized 不可响应中断
wait,notify,notifyall 配合synchronized使用
- Post title:Synchronized关键字
- Post author:大黄
- Create time:2023-11-23 17:41:30
- Post link:https://huangbangjing.cn/2023/11/23/Synchronized关键字/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.