线程进阶 --- synchronized 和 ReentrantLock

简介: 线程进阶 --- synchronized 和 ReentrantLock

一、synchronized 锁

sychronized 加锁是一个自适应的过程,减少了程序员使用时的负担。会根据情况依次进行锁升级,无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁。

1. 偏向锁


一个例子:

A和B是情侣  A答应B不结婚也会一直在一起------>这就是偏向锁,避免了高成本的结婚操作(加锁)。而有一天  一个帅哥出现在了A的身边 ,这时候就有必要结婚(加锁)。


  • 并不是真正的加锁,只是一个名分。
  • 偏向锁不是真的“加锁”,只是给对象头中做一个“偏向锁的标记”,记录这个锁属于哪个线程。
  • 偏向锁本质上属于“延迟加锁”,也就是说 能不加锁就不会加锁,从而来避免不必要的加锁开销。


2. 轻量级锁(自适应的自旋锁)

倘若偏向锁不够用,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。


  • 轻量级锁通过CAS来实现的,通过CAS检查并更新一块内存(比如null => 该线程的使用),如果更新成功,则认为加锁成功。如果更新失败,则认为锁被占用,一直自旋式的等待。
  • 轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。
  • 轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

3. 重量级锁


如果竞争进一步激烈,自旋锁也不够用,不能快速获取到锁的状态,就会膨胀为重量级锁。这里的重量级锁就是指 内核提供的 mutex

  • 执行加锁操作,先进入内核态
  • 在内核态判定当前锁是否已经被占用
  • 如果锁没有被占用,则说明加锁成功,并切回到用户态
  • 如果被占用了,加锁失败,此时进入锁的等待队列,挂起,等待被操作系统唤醒
  • 经历了一些沧海桑田,这个锁就被释放后,操作系统也想起了这个挂起的线程,于是唤醒这个线程,并且尝试重新获取锁

二、ReentrantLock

1. ReentrantLock 相关介绍

属于 JUC(java.util.concurrent ) 中常见的类

是一个可重入互斥锁。 功能和 synchronized 一样 都是用来实现互斥效果,保证线程安全。

2. ReentrantLock 的用法:


  • lock () :加锁,如果获取不到锁就 死等
  • trylock ( 超时时间 ) :尝试加锁加锁,能加酒驾,不能加就不加。可以设置等待时间
  • unlock() :解锁
  • 可以实现公平锁:ReentranLock 默认是不公平的,如果在构造的时候传入一个 true 参数就编程公平锁了。(      ReentrantLock locker = new ReentrantLock(true)     )


三、ReentrantLock 和 synchronized 的区别


  • synchronized 是一个关键字,JVM内部实现的。ReentrantLock 是一个标准库中的类,在JVM外使用 java 代码实现。
  • synchronized 使用的时候不需要手动释放锁,ReentrantLock 使用需要手动释放锁。


  • synchronized 申请锁失败会一直等带别的线程释放锁,ReentrantLock 可以通过trylock 方法等待一段时间就放弃申请锁。
  • synchronized 是非公平锁,ReentrantLock 默认是非公平锁,但可以构造的时候传一个 true 开启公平锁模式。
  • synchronized 是通过Object的 wait 、notify 实现 等待、唤醒,每次唤醒的是一个随机的进程。ReentrantLock 搭配 Condition 类 实现 等待、唤醒,可以更加精确的唤醒某个指定的线程。

四、我们使用的时候如何选择呢?


  • 锁竞争不激烈的时候,使用 synchronized,效率更高,自动释放更方便
  • 锁竞争激烈的时候,使用 ReentrantLock,搭配 trylock 更灵活控制加锁的行为,不会死等
  • 如果需要公平锁,使用 ReentrantLock  
相关文章
|
2月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
60 5
|
2月前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
40 0
|
2月前
|
Java
【多线程面试题十六】、谈谈ReentrantLock的实现原理
这篇文章解释了`ReentrantLock`的实现原理,它基于Java中的`AbstractQueuedSynchronizer`(AQS)构建,通过重写AQS的`tryAcquire`和`tryRelease`方法来实现锁的获取与释放,并详细描述了AQS内部的同步队列和条件队列以及独占模式的工作原理。
【多线程面试题十六】、谈谈ReentrantLock的实现原理
|
2月前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
82 5
|
2月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
22 2
|
2月前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
Java多线程同步实战:从synchronized到Lock的进化之路!
89 1
|
2月前
|
Java 开发者
Java多线程教程:使用ReentrantLock实现高级锁功能
Java多线程教程:使用ReentrantLock实现高级锁功能
35 1
|
2月前
|
Java C++
【Java 并发秘籍】synchronized vs ReentrantLock:揭秘线程同步神器的对决!
【8月更文挑战第24天】本文详细对比了Java并发编程中`synchronized`关键字与`ReentrantLock`的不同之处。`synchronized`作为内置关键字,提供自动锁管理但不支持中断或公平锁;`ReentrantLock`则通过显式调用方法控制锁,具备更多高级功能如可中断、公平锁及条件变量。文章通过两个计数器类实例展示了两种机制的具体应用,帮助读者理解其差异及适用场景。掌握这两者对于提升多线程程序设计能力至关重要。
43 0
|
2月前
|
存储 安全 Java
【多线程面试题十七】、如果不使用synchronized和Lock,如何保证线程安全?
这篇文章探讨了在不使用`synchronized`和`Lock`的情况下保证线程安全的方法,包括使用`volatile`关键字、原子变量、线程本地存储(`ThreadLocal`)以及设计不可变对象。
|
2月前
|
Java
【多线程面试题十五】、synchronized可以修饰静态方法和静态代码块吗?
这篇文章讨论了Java中的`synchronized`关键字是否可以修饰静态方法和静态代码块,指出`synchronized`可以修饰静态方法,创建一个类全局锁,但不能修饰静态代码块。