synchronized原理

简介: synchronized原理

基本特点

结合之前总结的锁策略,我们就可以总结出,synchronized具有以下特性(jdk1.8):

1.开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁.

2.开始是轻量级实现,如果锁被持有的时间较长,就转换为重量级锁.

3.实现轻量级锁的时候大概率用到自旋锁策略

4.是一种不公平锁

5.是一种可重入锁

6.不是读写锁.

加锁加工过程

JVM将synchronized锁分为无锁,偏向锁,轻量级锁,重量级锁状态.会根据情况,依次进行升级.

注意基本点:锁只能升级,不能降级,非必要不加锁.

偏向锁

第一个尝试加锁的线程,优先进入偏向锁状态.

偏向锁并不是真的"加锁",只是给对象头做一个"偏向锁的标记",记录这个锁属于哪个线程.如果后续没有其它线程来竞争该锁,那么就不用进行其他同步操作了(避免了加锁解锁的开销).如果后续有其它线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了,很容易识别当前申请锁的线程是不是之前记录的线程),那就取消原来偏向锁的状态,进入一般轻量级锁的状态,进入一般轻量级锁的状态.

偏向锁本质上相当于"延迟加锁",能不加锁就不加锁,尽量来避免不必要的加锁开销.

但是该做的标记还是得做的,否则无法区分何时需要真正加锁.

轻量级

随着其它线程进入竞争,偏向锁状态被消除,进入轻量级锁的状态(自适应的自旋锁).

此处的轻量级锁通过CAS实现(认识过程即可,CAS后面会讲)

1.通过CAS检查并更新一块内存(比如null->该线程引用)

2.如果更新成功,则认为加锁成功

3.如果更新失败,则认为锁被占用,继续自旋式地等待(并不放弃CPU).

自旋操作是一直让CPU空转,比较浪费CPU资源.

因此此处的自旋不会一直持续进行,而是达到了一定的时间/重试次数,就不再自旋了.

也就是所谓的"自适应".

于此同时synchronized内部也会统计当前锁对象上,有多少线程参与竞争,如果整体CPU消耗大,则会转为重量级锁.

重量级锁

如果竞争进一步激烈,自旋不能快速获取到锁状态,就会膨胀为重量级锁.

此处的重量级锁就是指用到内核提供的mutex.

1.执行加锁操作,先进入内核态

2.在内核态判定当前锁是否已经被占用

3.如果该锁没有占用,则加锁成功,并切换回用户态.

4.如果该锁被占用,则加锁失败.此时线程进入锁的等待队列,挂起.等待被操作系统唤醒.

5.经历了一系列的沧海桑田,这个锁已经被线程释放了,操作系统也想起了这个挂起的线程,于是唤醒了这个线程,尝试重新获取到锁.

其它的优化操作

锁消除

也是synchronized中内置的优化策略,代码不需要加锁就会删除.

编译器+JVM判断锁是否可以消除,如果可以,就直接消除.

什么是"锁消除"

编译器优化的一种方式,编译器编译代码时,如发现该代码不需要加锁,就会把锁自动干掉.

有些应用程序的代码中,用到了synchronized,但其实没有在多线程环境下.(例如StringBuffer)

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");

此时每个append的调用都会涉及到加锁和解锁.但如果只是在单线程中执行这个代码,那么这些加锁解锁是没必要的,白白浪费了一些资源开销.

锁粗化

一段逻辑中如果多次出现加锁解锁,编译器+JVM会自动进行锁的粗化.

即讲多个细粒度的锁,合并成一个粗粒度的锁.(代码越少越细,越多越粗).

锁的粒度:粗和细

通常情况下,其实是更偏好让锁的粒度更细一些,更有利于多个线程并发执行的.

但是实际上可能并没有其它线程来抢占这个锁.这种情况JVM就会自动把锁粗化,避免频繁申请释放锁.

举个例子理解锁粗化:

领导给下属交代工作两种方式:

方式1:

打电话,交代任务1,挂电话.

打电话,交代任务2,挂电话.

打电话,交代任务3,挂电话.

方式2:

打电话,交代任务1,交代任务2,交代任务3,挂电话.

显然方式2更为高效.

相关面试题

什么是偏向锁?

偏向锁并不是真的加锁,而只是在锁的对象头中记录一个标记(记录该锁所属的线程).如果没有其它线程参与竞争锁,那么就不会真正执行加锁操作,从而降低程序开销.一旦真的涉及到其它的线程竞争,再取消偏向锁的状态,进入轻量级锁的状态.

相关文章
|
6月前
|
Java
【多线程系列】你先说说synchronized的实现原理
面试官:听说你精通多线程,那我就考考你吧面试官:不用慌尽管说,错了也没关系😊。。。❤️。
【多线程系列】你先说说synchronized的实现原理
|
6月前
|
存储 监控 安全
Synchronized 实现原理
Synchronized 实现原理
60 0
|
3月前
|
存储 Java 程序员
synchronized的原理以及与ReentrantLock的区别
`synchronized`和`ReentrantLock`均为Java线程同步机制,确保共享资源的单一时刻独占访问。`synchronized`关键字直接嵌入JVM,可通过修饰方法或代码块实现对象锁或监视器锁,具备可重入性,依赖Mark Word进行锁状态管理。`ReentrantLock`则需显式调用`lock()`和`unlock()`,提供更灵活控制,如公平锁、尝试锁及条件变量。两者在语法、灵活性和异常处理上有所差异,但均支持可重入性。性能方面,随JDK优化,`synchronized`在某些场景下甚至优于`ReentrantLock`。选择使用哪个取决于具体需求和上下文。
|
1月前
|
安全 Java
Synchronized是怎么实现的?
Synchronized是怎么实现的?
|
1月前
|
Java 编译器 程序员
【多线程】synchronized原理
【多线程】synchronized原理
57 0
|
2月前
|
缓存 Java 编译器
JAVA并发编程synchronized全能王的原理
本文详细介绍了Java并发编程中的三大特性:原子性、可见性和有序性,并探讨了多线程环境下可能出现的安全问题。文章通过示例解释了指令重排、可见性及原子性问题,并介绍了`synchronized`如何全面解决这些问题。最后,通过一个多窗口售票示例展示了`synchronized`的具体应用。
|
5月前
|
存储 Java C++
Synchronized底层原理
Synchronized底层原理
|
5月前
|
存储 Java
Java并发编程 Synchronized原理
Java并发编程 Synchronized原理
24 0
|
6月前
|
存储 安全 Java
Synchronized锁工作原理
Synchronized锁工作原理
|
存储 安全 Java
synchronized 的底层原理
synchronized 的底层是通过 Java 中的监视器锁(monitor)来实现的。每个 Java 对象都有一个与之对应的监视器锁,当一个线程获取了该对象的监视器锁,就可以执行 synchronized 代码块或方法。其他线程只能等待该线程释放锁,才能获取该对象的监视器锁并执行 synchronized 代码块或方法。
103 0
synchronized 的底层原理