深入理解java中的AQS和synchronized

基础

1 synchronized 原理是什么: synchronized 原理是什么

2 AQS加锁解锁过程详解:
AQS加锁解锁过程详解

synchronized原理,讲一下底层实现一开始我也就标准的说了下锁升级的过程,也说了mutex lock。最后面试官问到比较深,问我moniter里面啥内容,我就记得waitsets,entrylist,owner,count这几个

synchronized 是 Java 中的关键字,用于控制对某个对象进行互斥访问。在多线程环境下,多个线程可能会同时访问同一段代码或同一个对象,这就可能会导致一些并发问题。通过使用 synchronized 关键字,可以确保在任何时刻只有一个线程可以访问被 synchronized 修饰的代码块或方法。

synchronized 的实现主要依赖于 JVM 底层的 monitor(监视器)机制。在 Java 中,每个对象都有一个与之关联的 monitor,当一个线程执行到被 synchronized 修饰的代码块或方法时,它需要先获取这个对象的 monitor,才能进入这个代码块或方法。

你提到的 monitor 中包含的部分内容包括:

  1. Owner: 指向拥有 monitor 的线程,当一个线程成功获取 monitor 后,Owner 就指向这个线程。

  2. EntryList: 包含了所有请求获取 monitor,但还没有成功获取到的线程。当一个线程尝试获取一个已经被其他线程拥有的 monitor 时,这个线程就会被放入 EntryList,并进入阻塞状态。

  3. WaitSet: 包含了所有执行了 Object.wait() 方法,正在等待被唤醒的线程。当一个线程执行了 Object.wait() 方法后,它会释放掉 monitor 并进入 WaitSet,等待其他线程调用 Object.notify() 或 Object.notifyAll() 方法将其唤醒。

  4. Recursion count: 当一个线程获取 monitor 后,可以多次重入被 synchronized 修饰的代码块或方法,recursion count 就用来记录这个线程重入 monitor 的次数。

当一个线程执行完 synchronized 代码块或方法后,它会释放 monitor,然后 JVM 会从 EntryList 中选择一个线程,将 monitor 的所有权转给这个线程。这样,这个线程就可以进入 synchronized 代码块或方法,开始执行。

需要注意的是,Java 中的 synchronized 是可重入的,也就是说,一个线程可以多次获取同一个 monitor。这是通过 recursion count 来实现的,每次重入,recursion count 就会加 1,每次退出,recursion count 就会减 1,当 recursion count 为 0 时,线程才会真正释放 monitor。

synchronized的monitor对象更像是依赖于操作系统底层的mutex lock但是AQS更像是在应用层级别实现锁机制对吗?

你的理解基本上是正确的。但还需要明确一点,那就是 Java 中的 synchronized 关键字和 java.util.concurrent.locks 包中的 Lock 接口(包括它的实现类,如 ReentrantLock)都是基于 Java 层面的,并且在 JVM 中都有对应的实现。

synchronized 的实现,尤其是在 JVM 早期版本,确实依赖于操作系统底层的 mutex lock,也就是经常被称为 “重量级锁”。但是在 JVM 的后续版本中(如 1.6+),为了提高性能,引入了锁的偏向、轻量级锁、自旋锁等优化技术,从而尽可能地避免进入到操作系统级别的线程阻塞。

而 AbstractQueuedSynchronizer(AQS)是在 Java 应用层面实现的一种用于构建锁和同步组件的框架,比如 ReentrantLockSemaphoreCountDownLatchFutureTask 等都是基于 AQS 实现的。AQS 内部通过一个双向链表(Condition Queue)和一个 FIFO 队列(Sync Queue)来维护等待的线程。因此,AQS 提供了一种更为高级和灵活的同步机制,并且相比于直接使用 synchronized,它提供了更多的控制选项(如可中断的获取、公平和非公平模式、超时获取等)。

综上,我们可以认为 synchronized 是 JVM 对于并发控制的基本实现,而基于 AQS 的锁机制(如 ReentrantLock)提供了更为丰富和灵活的同步控制方式。这两者在具体使用时需要根据具体的并发场景和需求来选择。

对于monitor对象,EntryList相当于就绪队列,存储所有被唤醒但没有获取到锁的线程,WaitSet相当于等待队列,等待I/O操作的完成,对不对

对于 Java 中的 monitor 对象,你的理解基本上是正确的。在 monitor 对象中,EntryList 是一个存储尝试获取 monitor 锁但是没有成功的线程的队列,当 monitor 锁被释放的时候,这些线程将被唤醒并重新尝试获取锁。WaitSet 是一个存储调用了 Object.wait() 方法而进入阻塞状态的线程的集合,这些线程等待的是其它线程调用相应对象的 notify() 或者 notifyAll() 方法,这通常涉及到等待某个条件(而不仅仅是等待 I/O 操作)的满足。

在 AQS 中,实际上有两个队列,一个是等待获取同步状态(锁)的队列(也就是 Sync

为什么AQS中只有一个双向链表嘛,没有等待队列?

在 AQS 中,实际上有两个队列,一个是等待获取同步状态(锁)的队列(也就是 Sync Queue),另一个是 Condition 对象的等待队列(也就是 Condition Queue)。两个队列的使用场景不同:Sync Queue 主要用于实现锁机制(例如在 ReentrantLock 中使用),Condition Queue 主要用于实现条件变量(例如在 Condition 中使用)。AQS 负责维护 Sync Queue,而 Condition 对象负责维护 Condition Queue。在 AQS 中使用 Condition 对象来支持一个锁有多个等待队列(Condition Queue),每个 Condition Queue 对应一个特定的条件。

AQS 使用一个双向链表来维护 Sync Queue,每个节点(Node)代表一个等待获取锁的线程。每个节点有一个 status 字段,用于存储当前线程的等待状态。同时,Node 还包含前驱和后继节点的引用,以便快速遍历队列。在锁被释放时,会唤醒队列头部的线程,而在获取锁失败时,线程将被插入到队列的尾部。这样,就可以保证 FIFO(First In First Out)的公平性。

AQS中的 Condition Queue和 Sync Queue 能同时使用吗?

是的,AQS(AbstractQueuedSynchronizer)中的Condition QueueSync Queue是可以同时使用的。实际上,这两种队列在某些情况下就是需要同时使用的。

ReentrantLock为例,当一个线程获取锁失败时,它将被放入到Sync Queue中等待;当一个线程在锁已经被获取的情况下,需要等待某个条件成立,它可以调用Condition.await()方法将自己放入到Condition Queue中等待。

然后,当其他线程释放锁时,AQS将唤醒Sync Queue中的一个线程;当其他线程调用Condition.signal()方法时,会选择一个在Condition Queue中等待的线程,将其移动到Sync Queue中,以便当锁可用时能被唤醒。

所以说,在AQS中,Condition QueueSync Queue是可以同时使用的,而且它们共同参与了线程的等待和唤醒过程。

AQS中的 Condition Queue和 Sync Queue 必须被同时使用吗?

AQS中的Condition Queue和Sync Queue并不需要必须同时使用。它们的使用取决于具体的同步组件如何利用AQS框架。

有没有分开使用这两个队列的场景?

可以分开使用这两个队列。例如,ReentrantLock或Semaphore这样的同步组件主要使用Sync Queue,当线程无法立即获取锁或许可时,它们将线程放入Sync Queue进行排队等待。另一方面,当我们使用Condition对象(通常与ReentrantLock结合使用)进行线程间通信时,会使用到Condition Queue。当一个线程调用Condition的await()方法时,它会被放入Condition Queue。

对于AQS,如果一个线程一开始获取锁失败,是先进入哪一个队列?

如果一个线程一开始获取锁失败,它会先进入Sync Queue。Sync Queue是AQS中用来管理等待获取锁的线程的队列。只有当线程已经获取到锁,但是需要等待某个条件满足时,才会使用Condition对象将线程放入Condition Queue。

Lock和synchronized的区别

猜你喜欢

转载自blog.csdn.net/yxg520s/article/details/131819176