java.util.concurrent.locks.AbstractQueuedSynchronizer构造同步类

AbstractQueuedSynchronizer是一个同步架构, Java中有很多同步类是由这个架构实现,比如FutureTask、ReentrantLock等。为了使用这个类实现一个同步架构,需要将其定义为一个帮助子类,需要适当地重新定义以下方法,这是通过使用getState 、 setState、或compareAndSetState 方法来检查和/或修改同步状态来实现的:

您也可以查找从 AbstractOwnableSynchronizer 继承的方法,用于跟踪拥有独占同步器的线程。鼓励使用这些方法,这允许监控和诊断工具来帮助用户确定哪个线程保持锁。

即使此类基于内部的某个 FIFO 队列,它也无法强行实施 FIFO 获取策略。独占同步的核心采用以下形式:

 Acquire:
     while (!tryAcquire(arg)) {
        enqueue thread if it is not already queued;
        possibly block current thread;
     }

 Release:
     if (tryRelease(arg))
        unblock the first queued thread;
 
(共享模式与此类似,但可能涉及级联信号。)

因为要在加入队列之前检查线程的获取状况,所以新获取的线程可能闯入 其他被阻塞的和已加入队列的线程之前。不过如果需要,可以内部调用一个或多个检查方法,通过定义 tryAcquire 和/或 tryAcquireShared 来禁用闯入。特别是 getFirstQueuedThread() 没有返回当前线程的时候,严格的 FIFO 锁定可以定义 tryAcquire 立即返回 false。只有 hasQueuedThreads() 返回 true 并且 getFirstQueuedThread 不是当前线程时,更好的非严格公平的版本才可能会立即返回 false;如果 getFirstQueuedThread 不为 null 并且不是当前线程,则产生的结果相同。出现进一步的变体也是有可能的。

对于默认闯入(也称为 greedyrenouncementconvoy-avoidance)策略,吞吐量和可伸缩性通常是最高的。尽管无法保证这是公平的或是无偏向的,但允许更早加入队列的线程先于更迟加入队列的线程再次争用资源,并且相对于传入的线程,每个参与再争用的线程都有平等的成功机会。此外,尽管从一般意义上说,获取并非“自旋”,它们可以在阻塞之前对用其他计算所使用的 tryAcquire 执行多次调用。在只保持独占同步时,这为自旋提供了最大的好处,但不是这种情况时,也不会带来最大的负担。如果需要这样做,那么可以使用“快速路径”检查来先行调用 acquire 方法,以这种方式扩充这一点,如果可能不需要争用同步器,则只能通过预先检查 hasContended() 和/或 hasQueuedThreads() 来确认这一点。

通过特殊化其同步器的使用范围,此类为部分同步化提供了一个有效且可伸缩的基础,同步器可以依赖于 int 型的 state、acquire 和 release 参数,以及一个内部的 FIFO 等待队列。这些还不够的时候,可以使用 atomic 类、自己的定制 Queue 类和 LockSupport 阻塞支持,从更低级别构建同步器。 


使用示例

以下是一个非再进入的互斥锁类,它使用值 0 表示未锁定状态,使用 1 表示锁定状态。当非重入锁定不严格地需要当前拥有者线程的记录时,此类使得使用监视器更加方便。它还支持一些条件并公开了一个检测方法:

 class Mutex implements Lock, java.io.Serializable {

    // Our internal helper class
    private static class Sync extends AbstractQueuedSynchronizer {
      // Report whether in locked state
      protected boolean isHeldExclusively() { 
        return getState() == 1; 
      }

      // Acquire the lock if state is zero
      public boolean tryAcquire(int acquires) {
        assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
      }

      // Release the lock by setting state to zero
      protected boolean tryRelease(int releases) {
        assert releases == 1; // Otherwise unused
        if (getState() == 0) throw new IllegalMonitorStateException();
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
      }
       
      // Provide a Condition
      Condition newCondition() { return new ConditionObject(); }

      // Deserialize properly
      private void readObject(ObjectInputStream s) 
        throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
      }
    }

    // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();

    public void lock()                { sync.acquire(1); }
    public boolean tryLock()          { return sync.tryAcquire(1); }
    public void unlock()              { sync.release(1); }
    public Condition newCondition()   { return sync.newCondition(); }
    public boolean isLocked()         { return sync.isHeldExclusively(); }
    public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
    public void lockInterruptibly() throws InterruptedException { 
      sync.acquireInterruptibly(1);
    }
    public boolean tryLock(long timeout, TimeUnit unit) 
        throws InterruptedException {
      return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
 }
看公有方法,是我们所熟悉的几个同步类的方法,他们封装了sync的方法。

1. lock(), 这个方法调用了sync.acquire(1)获取了独占锁acquire的定义如下

public final void acquire(int arg)
以独占模式获取对象,忽略中断。通过至少调用一次 tryAcquire(int) 来实现此方法,并在成功时返回。否则在成功之前,一直调用 tryAcquire(int) 将线程加入队列,线程可能重复被阻塞或不被阻塞。可以使用此方法来实现 Lock.lock() 方法。

参数:
arg - acquire 参数。此值被传送给 tryAcquire(int),但它是不间断的,并且可以表示任何内容。

     

我们看到acquire是通过调用tryAcquire(int)来实现的,直到成功返回时结束,因此我们无需要自定义这个方法就可用它来实现lock。

 

2.tryLock()是由sync.tryAcquire(1)来实现

 

3.unlock()由Release(int)来实现,我们看下release的定义

public final boolean release(int arg)
以独占模式释放对象。如果 tryRelease(int) 返回 true,则通过消除一个或多个线程的阻塞来实现此方法。可以使用此方法来实现 Lock.unlock() 方法

参数:
arg - release 参数。此值被传送给 tryRelease(int),但它是不间断的,并且可以表示任何内容。
返回:
tryRelease(int) 返回的值
这个类似与tryAcquire(int)
剩下方法分别调用了AQS的其他方法,不再一一介绍,下面看下tryAcquire(int)方法的实现
 // Acquire the lock if state is zero
      public boolean tryAcquire(int acquires) {
        assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
      }

我们看到首先去compareAndSetState(0, 1),如果满足,则setExclusiveOwnerThread(Thread.currentThread()),这个方法是AQS的父类
AbstractOwnableSynchronizer的方法,这是个作为监控作用的类。之后成功以后,返回true,否则返回false。
总结
当类库提供的同步类不能满足我们的要求的时候,或者我们需要关注类实现细节的时候,我们需要对AQS类有所了解,这是个同步框架,用来实现基于状态的同步类,其中最关键的方法是getState()、setState()、compareAndSetState(),这三个方法由架构提供,我们需要调用这三个方法来实现tryAcquire等方法来建立我们自定义的同步类,当然在一般情况下类库提供的同步类已足够我们所使用。





<think>我们正在分析Java线程阻塞或等待的堆栈跟踪,特别是在遇到死锁、性能问题或线程挂起时。常见的堆栈中会出现如`sun.misc.Unsafe.park`、`LockSupport.parkNanos`、`AQS`(AbstractQueuedSynchronizer)的`ConditionObject.awaitNanos`等方法。这些通常与线程的阻塞和唤醒机制有关。 根据引用[1]和[2],我们知道: - `LockSupport`是Java中用于线程阻塞和唤醒的工具类,它基于“许可”(permit)机制。 - 每个线程都有一个与之关联的许可(permit),其值只能是0或1,默认是0。 - 当调用`LockSupport.park()`时,如果许可可用(为1),则立即返回,并将许可置为0;否则,当前线程会被阻塞。 - 当调用`LockSupport.unpark(Thread thread)`时,如果该线程的许可不可用(为0),则使其变为可用(1)。如果该线程因为`park`而阻塞,则会被唤醒。 在`java.util.concurrent.locks`包中,`AbstractQueuedSynchronizer`(AQS)是构建锁和同步器的基础框架,它使用`LockSupport`来阻塞和唤醒线程。例如,在条件变量`ConditionObject`中,`await`方法会让线程阻塞,直到被信号唤醒或中断。 当我们在线程堆栈中看到`sun.misc.Unsafe.park`时,这通常是因为`LockSupport.park`方法最终调用了`Unsafe.park`方法(引用[1])。同样,`LockSupport.parkNanos`用于带有超时的阻塞。 现在,我们分析线程阻塞的原因: 1. **正常等待条件满足**:线程可能正在等待某个条件(如锁的释放、任务队列非空等)。例如,在`ReentrantLock`中,如果锁被其他线程持有,当前线程会进入AQS的等待队列并阻塞。当锁释放时,会唤醒等待的线程。 2. **等待超时**:使用`parkNanos`或`awaitNanos`等方法,线程会阻塞直到超时时间到达或者被唤醒。 3. **死锁**:多个线程互相等待对方释放锁,导致所有线程都无法继续执行。此时,线程堆栈中会显示多个线程在等待锁。 4. **活锁**:线程不断改变状态而无法继续执行,但堆栈可能显示为正常的等待状态(较少见)。 5. **资源不足**:例如,线程池任务队列已满,提交任务的线程被阻塞(如使用`SynchronousQueue`时)。 6. **中断状态**:线程在阻塞过程中被中断,但中断处理可能没有正确唤醒线程。 7. **未唤醒**:线程被`park`阻塞,但后续没有对应的`unpark`来唤醒它(可能是编程错误,如条件判断错误导致未调用唤醒方法)。 在分析堆栈时,我们需要关注: - 线程的状态:`java.lang.Thread.State`。例如,`WAITING (parking)`表示线程在`park`上等待。 - 堆栈的调用链:查看线程阻塞在哪个对象上(如锁对象、条件对象),以及阻塞的原因(如等待条件、等待锁)。 - 结合日志和代码:查看在阻塞点之前的代码逻辑,判断是否满足唤醒条件。 例如,一个典型的AQS条件等待的堆栈可能如下: ``` "Thread-0" #12 prio=5 os_prio=0 tid=0x000000001d9e8000 nid=0x1d34 waiting on condition [0x000000001e4ef000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000d5d5c4d0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at com.example.MyService.doSomething(MyService.java:123) ... ``` 这里,线程在`ConditionObject.await`方法中阻塞,等待条件变量(地址为`0x00000000d5d5c4d0`)的信号。我们需要检查代码中是否有对应的`signal`或`signalAll`调用,以及条件判断是否正确。 **排查步骤**: 1. 收集所有线程的堆栈(使用jstack或jcmd)。 2. 查找处于`WAITING`、`TIMED_WAITING`或`BLOCKED`状态的线程。 3. 分析每个阻塞线程的堆栈,确定阻塞点(如锁、条件变量、IO等)。 4. 检查资源竞争和依赖关系,特别是锁的持有和等待关系,以判断死锁。 5. 检查代码逻辑,确保在适当的时候调用唤醒方法(如`unpark`、`signal`、`signalAll`)。 6. 使用工具(如jconsole、VisualVM)监控线程状态和锁情况。 如果遇到死锁,堆栈中通常会显示多个线程互相等待锁。例如: ``` Thread A: waiting to lock <0x00000000d5d5c4d0> (a com.example.Resource) held by Thread B Thread B: waiting to lock <0x00000000d5d5c4e0> (a com.example.Resource) held by Thread A ``` 此时,需要调整锁的获取顺序,或者使用死锁检测和恢复机制。 对于非死锁的线程挂起,可能是由于未唤醒导致。需要检查条件变量的使用是否正确,确保在条件满足时调用唤醒方法。 **相关问题**: 1. 如何收集和分析Java线程堆栈? 2.Java中,如何诊断死锁? 3. 除了`LockSupport`,Java中还有哪些线程阻塞和唤醒的机制? 4. 如何避免线程因未唤醒而永久挂起? 5. 在AQS中,条件变量`ConditionObject`的使用有哪些注意事项?</think>### Java线程阻塞堆栈分析:`sun.misc.Unsafe.park`与相关机制 当线程堆栈中出现`sun.misc.Unsafe.park`、`LockSupport.parkNanos`、`AQS ConditionObject.awaitNanos`等调用时,通常表示线程因等待资源而主动进入阻塞状态。以下是关键原因分析: #### 1. **线程阻塞的核心机制** - **`LockSupport.park`系列方法**:线程阻塞的基础API(引用[2])。 通过**许可(permit)**机制工作: - 初始许可为0,`park()`消耗许可(若无许可则阻塞) - `unpark(thread)`发放许可(若线程阻塞则唤醒) - **`Unsafe.park`底层实现**: `LockSupport.park()`最终调用`Unsafe.park()`(引用[1]),通过操作系统原语实现线程挂起。 - **AQS同步框架**: `AbstractQueuedSynchronizer`(如ReentrantLock)和`ConditionObject`(条件变量)使用`LockSupport`管理线程阻塞队列(引用[1][4])。 #### 2. **常见阻塞场景与堆栈特征** | 场景 | 典型堆栈特征 | 原因分析 | |---------------------|---------------------------------------------|--------------------------------------------------------------------------| | **锁竞争** | `java.util.concurrent.locks.ReentrantLock$Sync.lock()` → `AQS.acquire()` → `LockSupport.park()` | 线程等待获取锁资源 | | **条件等待** | `ConditionObject.await()` → `LockSupport.park()` | 线程调用`condition.await()`主动释放锁并阻塞(如生产者-消费者模型) | | **超时等待** | `LockSupport.parkNanos(n)` 或 `ConditionObject.awaitNanos(n)` | 带超时的阻塞,常见于定时任务或避免永久阻塞 | | **线程池任务调度** | `ThreadPoolExecutor.getTask()` → `workQueue.take()` → `LockSupport.park()` | 工作线程等待新任务(如ScheduledThreadPoolExecutor) | | **资源耗尽** | `Semaphore.acquire()` → `AQS.acquireSharedInterruptibly()` → `LockSupport.park()` | 信号量许可不足时阻塞 | #### 3. **阻塞原因诊断步骤** 1. **定位阻塞对象** 检查堆栈中`parking to wait for <0x00000000d6d1f1d0>`(地址值),确定等待的锁/条件变量。 ```java // 示例堆栈片段 at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000d6d1f1d0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) ``` 2. **分析同步器状态** - 使用`jstack`或`jcmd`查看持有锁的线程(`locked <0x...>`) - 检查`ConditionObject`是否缺少`signal()`/`signalAll()`调用 3. **排查死锁** 若多个线程互相等待资源,结合堆栈信息绘制资源依赖图: ``` Thread-A: waiting to lock <0x00000000d6d1f1d0> (held by Thread-B) blocked on ConditionObject.await() Thread-B: waiting to lock <0x00000000d6d1f2e0> (held by Thread-A) blocked on ReentrantLock.lock() ``` 4. **检查超时设置** 若使用`parkNanos`/`awaitNanos`,确认超时值是否合理: - 过小→频繁唤醒消耗CPU - 过大→响应延迟 #### 4. **典型问题解决方案** - **死锁**:调整锁获取顺序,或使用`tryLock(timeout)`(引用[4]) - **未唤醒**:确保`await()`后必有`signal()`,且检查条件谓词(如while循环) ```java // 正确用法 while (!condition) { condition.await(); // 防止虚假唤醒 } ``` - **永久阻塞**:检查`unpark()`是否被遗漏,或使用守护线程+超时 - **性能优化**: 将`LockSupport.park()`替换为`parkNanos(1)`(自旋优化),但需测试CPU影响 #### 5. **工具推荐** - **`jstack`/`jcmd`**:捕获线程堆栈 - **Arthas**:实时监控线程状态,执行`thread -b`检测死锁 - **VisualVM**:图形化分析锁竞争 > 关键提示:`Unsafe.park()`是**非中断阻塞**,需配合`Thread.interrupt()`实现取消(抛出`InterruptedException`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值