Java并发编程--JUC并发工具类之LockSupport

摘要

  • 本文介绍LockSupport相关技术

  • 本文基于jdk1.8

LockSupport介绍

  • LockSupport是Java并发工具类中的一个重要成员。它提供了一种基于线程的阻塞和唤醒机制,使得线程可以在特定条件下暂停和继续执行。

  • 与传统的wait()notify()方法相比,LockSupport具有以下优势:

    • 精准的线程阻塞和唤醒:
      LockSupport提供了精确控制线程阻塞和唤醒的能力。通过调用LockSupport类的park()方法,线程可以主动进入阻塞状态,直到其他线程调用了相应线程的unpark()方法才能被唤醒。相比之下,wait()notify()方法的使用需要依赖于对象的监视器(monitor),并且无法指定特定的线程进行唤醒。
    • 不依赖于对象的监视器:
      传统的wait()notify()方法需要依赖于对象的监视器(monitor),即在synchronized块中调用。这种依赖关系可能导致代码结构上的限制,而LockSupport则不依赖于特定的对象,可以在任何位置进行线程的阻塞和唤醒操作。
    • 避免死锁问题:
      在使用wait()notify()方法时,由于需要依赖于对象的监视器,可能会出现死锁问题,例如线程A等待线程B的通知,而线程B也在等待线程A的通知,导致双方无法继续执行。LockSupport通过给每个线程关联一个许可(permit)来避免死锁问题,即使在park()unpark()方法的调用顺序上没有特定的要求。
  • 总体而言,LockSupport是一种强大而灵活的线程阻塞和唤醒机制,能够满足并发编程中的各种需求。它的设计理念与传统的wait()notify()方法有所不同,提供了更加直观和可控的线程调度方式。然而,在使用LockSupport时,仍需谨慎处理线程的阻塞和唤醒逻辑,以避免潜在的并发问题。

LockSupport API说明

方法 描述
void park() 阻塞当前线程,直到被唤醒
void park(Object blocker) 阻塞当前线程,并关联一个阻塞对象
void parkNanos(long nanos) 阻塞当前线程,最多等待指定纳秒时间
void parkNanos(Object blocker, long nanos) 阻塞当前线程,并关联一个阻塞对象,最多等待指定纳秒时间
void parkUntil(long deadline) 阻塞当前线程,直到指定的绝对时间点
void parkUntil(Object blocker, long deadline) 阻塞当前线程,并关联一个阻塞对象,直到指定的绝对时间点
void unpark(Thread thread) 唤醒指定的线程
Object getBlocker(Thread thread) 获取指定线程关联的阻塞对象
  • 其中,park()unpark()是最常用的方法,用于线程的阻塞和唤醒操作。

  • 其他方法提供了更多灵活的线程阻塞和唤醒方式,如指定阻塞对象、等待一定时间等。

  • getBlocker()方法可以获取指定线程关联的阻塞对象,以便进行进一步的操作或分析。

  • LockSupport的内部实现中,会使用底层的CAS操作来控制线程的阻塞和唤醒。当线程调用park()方法时,会将线程置于等待队列,并将线程的许可状态设置为负数。当调用unpark()方法时,会将线程的许可状态设置为非负数,从而唤醒等待的线程。

代码示例

示例1–park()unpark()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.concurrent.locks.LockSupport;

public class CASParkUnparkExample {
//
private static volatile boolean condition = false;

public static void main(String[] args) {
Thread waiterThread = new Thread(() -> {
while (!condition) {
// 阻塞当前线程
LockSupport.park();
}
System.out.println("Condition is true. Thread is awake.");
});

Thread notifierThread = new Thread(() -> {
// 模拟一些耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
condition = true;
// 唤醒等待的线程
LockSupport.unpark(waiterThread);
});

waiterThread.start();
notifierThread.start();
}
}
  • 在上面的示例中,waiterThread线程通过调用LockSupport.park()进入阻塞状态,直到condition变为truenotifierThread线程在经过一些耗时操作后,将condition设置为true,然后调用LockSupport.unpark(waiterThread)唤醒waiterThread线程。

示例2–park(Object blocker)getBlocker(Thread thread)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.concurrent.locks.LockSupport;

public class ParkBlockerExample {
public static void main(String[] args) throws InterruptedException {
Object blocker = new Object(); // 创建一个阻塞对象

Thread thread = new Thread(() -> {
System.out.println("Thread is parking...");
LockSupport.park(blocker); // 阻塞当前线程,并关联阻塞对象
System.out.println("Thread is unparked");
});

thread.start();

Thread.sleep(2000);

// 获取并输出关联的阻塞对象
Object associatedBlocker = LockSupport.getBlocker(thread);
System.out.println("Associated blocker: " + associatedBlocker);

// 唤醒线程
LockSupport.unpark(thread);
}
}
  • 在上述示例中,我们创建了一个阻塞对象blocker。在thread线程中,我们调用LockSupport.park(blocker)方法阻塞当前线程,并将阻塞对象与线程关联。

  • 在主线程中,我们使用LockSupport.getBlocker(thread)方法获取与线程关联的阻塞对象,并输出它。

park方法中阻塞对象的作用

  • 当调用LockSupport的park()方法时,线程将被阻塞,等待被唤醒。park(Object blocker)方法是park()方法的一个重载形式,它允许您将一个阻塞对象与当前线程相关联。绑定阻塞对象的作用在于更好地识别和监控线程的阻塞原因。

  • 当线程被阻塞时,可以通过调用getBlocker(Thread thread)方法来获取与该线程相关联的阻塞对象。这样,您可以在代码中根据阻塞对象进行更精确的条件判断、监控或其他操作。

  • 以下是一些使用场景的例子,说明绑定阻塞对象的作用:

    • 调试和诊断:当多个线程被阻塞时,可以使用阻塞对象来确定具体是哪个线程被阻塞,以及被阻塞的原因。通过获取阻塞对象,您可以在调试过程中定位问题,并根据阻塞对象的不同采取相应的调试措施。
    • 更精确的控制和唤醒:通过关联特定的阻塞对象,您可以在需要唤醒线程时,只选择唤醒与该阻塞对象相关联的线程。这种精确的唤醒机制可以避免不必要的线程唤醒,提高系统的性能和效率。
    • 条件等待:在某些情况下,您可能希望线程在特定条件下被阻塞,直到满足某个条件后才被唤醒。通过绑定阻塞对象,您可以根据不同的条件选择不同的阻塞对象,从而实现对不同条件的精确等待和唤醒。
  • 绑定阻塞对象的作用是提供更多的上下文信息,帮助您更好地理解和控制线程的阻塞状态。它可以作为一种辅助手段,用于更精确地调试、监控和控制线程的行为。

ReentrantLock的阻塞唤醒机制是基于LockSupport实现的

  • ReentrantLock是Java中提供的可重入锁的实现,它使用了内部类Sync来管理锁的状态和线程的阻塞与唤醒。Sync类内部使用了LockSupport工具类来实现线程的阻塞和唤醒操作。

  • 当一个线程无法获取到ReentrantLock的锁时,它会被阻塞,并且进入等待状态,在这种情况下,ReentrantLock内部会调用LockSupport.park()方法阻塞当前线程。

  • 当其他线程释放了锁或者调用了ReentrantLockunlock()方法时,被阻塞的线程将会被唤醒,此时ReentrantLock会通过调用LockSupport.unpark()方法来唤醒被阻塞的线程。