【多线程】synchronized原理

简介: 【多线程】synchronized原理

结合 锁策略,我们就可以总结出,synchronized具有以下特性:

  1. 乐观悲观,自适应
  2. 重量轻量,自适应
  3. 自旋挂起等待,自适应
  4. 非公平锁
  5. 可重入锁
  6. 不是读写锁

当代码执行到 synchronized 代码块中,JVM 大概要做哪些事情

一、锁升级 (面试经常考)

  • 刚开始 synchronized 加锁,首先锁会处于“偏向锁”状态
  • 遇到线程之间的锁竞争,升级到“轻量级锁”
  • 进一步的统计竞争出现的频次,达到一定程度之后,升级到“重量级锁”
    synchronized 加锁的时候,会经历:无锁 => 偏向锁 => 轻量级锁 => 重量级锁

轻量级锁和重量级锁在 各种锁策略这篇文章中有详细介绍

在这里我们理解一下什么是“偏向锁

偏向锁

偏向锁不是真的加锁,因为真的加锁开销比较大,偏向锁只是做个标记,标记的过程,非常的轻量高效(比轻量级锁还要轻量)

偏向锁不是真的"加锁",只是给对象头中做⼀个"偏向锁的标记",记录这个锁属于哪个线程

  • 如果后续没有其他线程来竞争该锁,那么就不⽤进⾏其他同步操作了(避免了加锁解锁的开销)
  • 如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了,很容易识别当前申请锁的线程是不是之前记录的线程),那就取消原来的偏向锁状态,就立即加锁,进⼊⼀般的轻量级锁状态
    偏向锁本质上相当于"延迟加锁",能不加锁就不加锁,尽量来避免不必要的加锁开销,但是该做的标记还是得做的,否则⽆法区分何时需要真正加锁

例子:

假设男主是⼀个锁,⼥主是⼀个线程。如果只有这⼀个线程来使⽤这个锁,那么男主⼥主即使不领证结婚(避免了⾼成本操作),也可以⼀直幸福的⽣活下去

但是⼥配出现了,也尝试竞争男主,此时不管领证结婚这个操作成本多⾼,⼥主也势必要把这个动作完成了,让⼥配死⼼

偏向锁 => 轻量级锁:出现竞争

轻量级锁 => 重量级锁:竞争激烈

上述锁升级的过程,主要也是为了能够让 synchronized 这个锁很好的适应不同的场景,就可以降低程序员的使用负担(程序员不用考虑太多,无脑使用 synchronized 就行了)

对于当前 JVM 的实现来说,上述锁升级的过程,属于“不可逆”(只能升级,不能降级),要想回到最初的状态,就需要再弄一个锁对象才可以

二、锁消除

编译器的优化策略


编译器会对你写的 synchronized 代码做出判定,判定这个地方是否确实需要加锁,如果这里没有必要加锁,就能够自动把 synchronized 给干掉(避免产生无意义的执行开销)

锁消除虽然存在,我们写代码的时候,也不能完全指望这个,无脑加锁

  • 锁消除只是一个辅助效果,给我们兜底,就别指望全给我们兜这个底
  • 毕竟编译器的判定有时候也没那么准确,尤其是在多线程环境下的代码

三、锁粗化

编译器的优化策略


锁的粒度

synchronized() {
  ...
}
  • 里面的代码越多,“粒度越粗”
  • 代码越少,“粒度越细”
  • 得看代实际执行的代码,而不是有多少行,可能里面有方法调用,也算“代码量”

锁粗化就是把多个“细粒度”的锁,合并成“粗粒度”的锁

  • 一次加解锁过程,就把四次操作全部涵盖进去了

因为每次加锁都可能涉及到阻塞等待,如果拆成多次,那么总的阻塞时间就可能很长,为了缩短总的等待时间,提高执行效率,就可以使用“锁粗化”的操作

例子:汇报工作

领导给你汇报了三个活,让你今天搞定

领导下班的时候,你把这三个活都干完了,去向领导电话汇报

  1. 三个任务给领导分别打三个电话进行汇报
  • 锁的粒度太小了,每次给领导打电话,就相当与对领导加锁,这样都被嫌死了
  • 每次加一个小粒度的锁,频繁加锁,效率是非常低的
  1. 三个任务打一个电话一起汇报
  • 这样也会缩短整体的阻塞时间

四、相关面试题


相关文章
|
4月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
91 5
|
4月前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
67 0
|
1月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
46 4
|
3月前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
150 29
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
2月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
28 2
|
2月前
|
Java 应用服务中间件 API
nginx线程池原理
nginx线程池原理
41 0
|
4月前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
90 5
|
4月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
32 2
|
3月前
|
存储 缓存 Java
JAVA并发编程系列(11)线程池底层原理架构剖析
本文详细解析了Java线程池的核心参数及其意义,包括核心线程数量(corePoolSize)、最大线程数量(maximumPoolSize)、线程空闲时间(keepAliveTime)、任务存储队列(workQueue)、线程工厂(threadFactory)及拒绝策略(handler)。此外,还介绍了四种常见的线程池:可缓存线程池(newCachedThreadPool)、定时调度线程池(newScheduledThreadPool)、单线程池(newSingleThreadExecutor)及固定长度线程池(newFixedThreadPool)。
|
4月前
|
存储 NoSQL Java
线程池的原理与C语言实现
【8月更文挑战第22天】线程池是一种多线程处理框架,通过复用预创建的线程来高效地处理大量短暂或临时任务,提升程序性能。它主要包括三部分:线程管理器、工作队列和线程。线程管理器负责创建与管理线程;工作队列存储待处理任务;线程则执行任务。当提交新任务时,线程管理器将其加入队列,并由空闲线程处理。使用线程池能减少线程创建与销毁的开销,提高响应速度,并能有效控制并发线程数量,避免资源竞争。这里还提供了一个简单的 C 语言实现示例。