多线程系列2-锁

锁的分类:

1、重入锁和不可重入锁

当前线程是否可以重入锁。synchronized、ReentrantLock、ReentrantReadWriteLock都是可重入锁。

2、乐观锁和悲观锁

用户态、内核态,jvm如果可以直接发指令让cpu去执行,不需要操作系统调度,那么这个就是用户态需要借助操作系统才能去执行指令。

悲观锁:获取不到锁资源时,会将当前线程挂起(进入BLOCKED、WAITING),线程挂起会涉及到用户态和内核太的切换,而这种切换是比较消耗资源的

线程的挂起和唤醒都需要借助操作系统去实现。所以就是从用户态切换到内核态。切换是有消耗的。

3、公平锁和非公平锁

一般获取方式:公平锁-> 先看线程state是否可以直接拿到锁。如果不可以,则看自己是不是链表的第一个,如果是则立即开始竞争。如果不是就排队。

非公平锁-> 先看线程state是否可以直接拿到锁。如果不可以,则再去竞争一次,如果还是竞争不上就去排队,排队时,看自己是不是链表的第一个,如果是则立即开始竞争。如果不是就排队。

 

 4、互斥锁和共享锁

 

深入synchronized关键字:

基于对象的

同步方法、同步代码块

 举例:

 synchronized优化:

无非就是锁消除、锁膨胀(‌锁粗化),升级过程如下:

单线程是可重入锁,是偏向锁。

如果有其他线程假如,那么就升级为轻量级锁(cas),如果获取不到,触犯为重量级锁。

 

 MarkWord的实现,对象头信息有锁的标志位。

 

为了看到对象的markword对象头信息:

需要导入依赖:

<dependency>
  <groupId>org.openjdk.jol</groupId>

  <artifactId>jol-core</artifactId>

  <version>0.9</version>

</dependency>

 在偏向锁升级为轻量级锁时,需要等一个安全点,假如jvm才开始启动的时候,那么这时候锁标志位是轻量级锁,假如已经是5秒过后,那么这个时候是偏向锁。

 

hpp文件(应该是c++定义类的文件),即C++代码的源码。

 

 

 

 实现了线程的挂起、唤醒、排队的一些列操作的方法。

代码结构图,主要有state、双向链表Node。

 

 非公平锁的效率更高,高于公平锁。

 非公平锁的效率更高,高于公平锁。

 非公平锁的效率更高,高于公平锁。 

 非公平锁的效率更高,高于公平锁。

AQS加锁流程:

线程C来了也会立即做CAS操作。所以叫非公平锁。

当线程获取锁失败,需要将自身挂起的时候的操作:

锁的waitStatus状态:

线程挂起过后,需要前一个节点来唤醒下一个节点

当waitStatus=Signal,表示下一个节点可以挂起。

如果waitStatus不正常,那么需要将自己的头结点指针往前跨一步。

1:表示线程已取消等待(如超时或中断),需从队列中移除

-1:表示下一个线程可以挂起,自己执行完过后唤醒下一个线程

0:初始状态

-2:节点处于条件队列(与Condition相关,非同步队列)

-3:共享模式下传播唤醒状态(ReentrantLock为独占模式,不涉及此状态)

 

UNSAFE.park(),表示挂起线程,将自身线程挂起,将running线程状态变成waiting状态。

 在AQS取消节点时,需注意:

 

保证前面的节点的waitStatus是-1,表示需要唤醒下一个节点。

 

 

 

释放锁:

 

 释放锁时需满足以下条件:

1、释放锁必须是要持有锁的当前线程去释放。比如t1持有的锁,main线程去释放就会失败

2、需判断state的值,state等于0表示释放干净,才会去释放,可重入的锁state会加1

3、去唤醒下一个节点的时候,需要满足waitStatus=-1的条件,这个是有效条件。

唤醒方法:UNSAFE.unpark(thread)。

 线程可以重复唤醒,不会报错。

唤醒方法注意:需判断head节点的waitStatus是否为-1,如果不为-1则终止操作。

当唤醒线程时,是要重新竞争锁

 这里是个死循环,会重复去获取锁。

reentrantLocal ConditionObject 介绍:


像synchronized提供了wait和notify的方法实现线程在持有锁时,可以实现挂起,已经唤醒的操作。

ReentrantLock也拥有这个功能。

ReentrantLock提供了await和signal方法去实现类似wait和notify的功能。

想执行await或者是signal就必须先持有lock锁的资源。

 

 

非常重要

非常重要

非常重要

condition.await():是将自身线程await过后,意思是从running状态进入waitting状态,进入单链表nextWaitor里,当condition.singal()这个方法过后,是将线程放入AQS双链表中,唤醒了需要重新竞争锁。

这里让线程等待的方法都是用的LockSupport.park(this);

在condition.singal()方法过后,恢复执行后,需要重新获取锁资源,走AQS那一套逻辑。当锁释放过后队列里的线程重新竞争。 

 

 

 

 

 

写锁的加锁流程:
1、写线程来竞争写锁资源
2、写线程会直接通过tryAcquire获取写锁资源(公平锁&非公平锁)
3、获取state值,并且拿到低16位的值。
4、如果state值为不为0,判断是否是锁重入操作,判断当前持有写锁的线程是否是当前线程
5、如果state值为0:
5.1、判断是否是公平锁:公平锁 -> 看自己是否是头结点.next,如果是我就抢,不是就排队。
5.2、判断是否是非公平锁: 非公平锁 -> 看自己是否是头结点,直接抢一手
6、如果拿到锁资源,直接告辞,如果没有拿到去排队,而排队的逻辑和ReentrantLock一样

偶然一看:

 

 

 

 

LockSupport是Java并发包中用于线程阻塞和唤醒的核心工具类,其底层实现基于sun.misc.Unsafe类和操作系统级线程调度46。以下是关键实现细节:


1. ‌核心方法实现‌

  • park()
    通过Unsafe.park()调用操作系统原语(如Linux的futex)阻塞当前线程,线程状态变为WAITING68。

    • 若线程已有许可(通过unpark()预先设置),则立即返回6。
    • 否则阻塞直到被唤醒或中断48。
  • unpark(Thread thread)
    通过Unsafe.unpark()唤醒指定线程,设置线程的许可标志为可用68。

    • 若线程尚未阻塞,则下次调用park()时不会阻塞6。
  • ‌带超时的方法(如parkNanos())‌
    类似park(),但通过纳秒级超时参数控制阻塞时长,底层依赖系统计时器68。


2. ‌许可机制‌

  • 每个线程关联一个二元许可状态(类似信号量)6:
    • unpark()设置许可为‌可用‌(最多累积1次)68。
    • park()消耗许可并立即返回,否则阻塞6。

3. ‌底层依赖‌

  • sun.misc.Unsafe
    直接操作线程内存和系统调用,绕过JVM限制48。
  • ‌操作系统原语‌
    如Linux的futex(快速用户空间互斥锁)实现高效线程挂起/唤醒68。

4. ‌与synchronized对比‌

  • ‌灵活性‌:无需绑定对象监视器,可直接控制线程48。
  • ‌可靠性‌:unpark()可先于park()调用,避免线程永久阻塞6。

5. ‌应用场景‌

  • 实现锁(如ReentrantLock)、条件变量(Condition)等高级同步工具46。
  • 构建自定义并发结构(如线程池任务调度)8。

完整实现可参考LockSupport类源码,其通过Unsafe和操作系统协作完成线程控制46。

 

 

 

 

LockSupport的底层实现机制主要分为以下几个层面:

  1. ‌许可机制核心设计‌
  • 采用二元信号量模型,每个线程关联一个虚拟的permit(许可证)
  • permit只有0/1两种状态,unpark()置1,park()消费后置023
  • 允许先unpark后park,解决传统wait/notify的时序问题28
  1. ‌用户态与内核态协作‌
  • 优先尝试用户态解决方案:
    • park()先检查permit状态,若为1则直接返回35
    • 通过自旋短暂等待避免立即进入内核阻塞3
  • 最终可能进入内核态:
    • 长时间阻塞时通过pthread_cond_wait等系统调用实现38
  1. ‌Unsafe类桥接‌
  • 核心方法通过Unsafe本地方法实现:
javaCopy Code
 
// HotSpot关键实现 public native void park(boolean isAbsolute, long time); public native void unpark(Thread thread);
  • 参数说明:
    • isAbsolute:时间单位是否为绝对时间(false表示相对时间纳秒)8
    • time:超时时间或截止时间戳58
  1. ‌与synchronized对比优势‌
  • 不依赖对象监视器,直接操作线程46
  • 精准唤醒指定线程(notify随机唤醒)26
  • 无死锁风险(许可机制避免资源占用)25
  1. ‌典型应用场景‌
  • AQS(AbstractQueuedSynchronizer)的底层支持16
  • 线程池工作线程的阻塞/唤醒4
  • StampedLock等高级锁的实现基础

 

 

 

 

 

 

 
posted on 2025-05-03 02:02  Hi Martin  阅读(37)  评论(0)    收藏  举报