了解LockSupport

63 阅读7分钟

LockSupport基本介绍

LockSupport 是 Java 中的一个实用工具类,它属于 java.util.concurrent.locks 包。这个类提供了基本的线程阻塞原语,这些原语用于构建同步器和锁。LockSupport 可以用于线程之间的协调,特别是当你需要阻塞一个线程直到另一个线程执行某个操作时。

LockSupport 特点

以下是 LockSupport 的一些关键点:

  1. 线程阻塞与唤醒LockSupport 提供了 parkunpark 方法来阻塞和唤醒线程。当调用 park 方法时,当前线程会阻塞,直到另一个线程调用相同线程对象的 unpark 方法。 park方法表示消耗一个许可,调用park方法时,如果许可可用则park方法返回,如果没有许可则一直阻塞直到许可可用。unpark方法表示增加一个许可,多次调用并不会积累许可,因为许可数最大值为1。
  2. 无锁编程LockSupport 可以用于实现无锁的编程模式,特别是在构建高性能的并发应用程序时。
  3. 公平性LockSupport 不保证公平性,也就是说,没有特定的顺序来唤醒等待的线程。如果需要公平性,可能需要使用其他同步机制。
  4. 简单性LockSupport 提供了一种简单的方法来阻塞和唤醒线程,不需要创建复杂的锁对象。
  5. 使用场景:它通常用于实现同步器,如信号量、屏障、倒计时锁等
  6. LockSupport是一个工具类,提供了基本的线程阻塞和唤醒功能,它是创建锁和其他同步组件的基础工具,内部是使用sun.misc.Unsafe类实现的。

LockSupport 例子加强理解

public class LockSupportExample {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("Thread is running");
            LockSupport.park(); // 线程将会阻塞在这里
            System.out.println("Thread continues after being unparked");
        });

        t.start();

        try {
            Thread.sleep(1000); // 等待1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        LockSupport.unpark(t); // 唤醒上面创建的线程
    }
}

LockSupport 顺序打印ABC

public class LockSupporTest {
    public static void main(String[] args) {
        // 创建三个线程,分别用于打印字符 'A'、'B' 和 'C'
        Thread threadC = new Thread(() -> printC());
        Thread threadB = new Thread(() -> printB(threadC)); // threadB 需要 threadC 作为参数
        Thread threadA = new Thread(() -> printA(threadB)); // threadA 需要 threadB 作为参数

        // 启动三个线程
        threadA.start();
        threadB.start();
        threadC.start();

        // 主线程进入一个无限循环,这里可能需要一个条件来打破循环,否则程序将无法退出
        while (true) {
            // 这里应该添加一个 break 条件,或者使用其他方式来结束程序,否则程序会一直运行
        }
    }

    // printA 方法,接收一个线程对象作为参数
    private static void printA(Thread thread) {
        // 让当前线程休眠 5000 毫秒
        ThreadUtil.sleep(5000);
        System.out.println("a");
        // 唤醒传入的线程对象(在本例中是 threadB)
        LockSupport.unpark(thread);
    }

    // printB 方法,同样接收一个线程对象作为参数
    private static void printB(Thread thread) {
        // 让当前线程休眠 2000 毫秒
        ThreadUtil.sleep(2000);
        // 当前线程阻塞,直到另一个线程调用它的 LockSupport.unpark()
        LockSupport.park();
        System.out.println("b");
        // 唤醒传入的线程对象(在本例中是 threadC)
        LockSupport.unpark(thread);
    }

    // printC 方法,没有接收参数
    private static void printC() {
        // 让当前线程休眠 1000 毫秒
        ThreadUtil.sleep(1000);
        // 当前线程阻塞,直到另一个线程调用它的 LockSupport.unpark()
        LockSupport.park();
        // 打印字符 'C'
        System.out.println("c");
    }
}

LockSupport面试题

关于 LockSupport 的一些常见面试题及其答案如下:

  1. LockSupport.park()Thread.sleep() 有什么区别?

    • LockSupport.park():它会暂停当前线程的执行,直到它被 unpark,线程被中断,或者出现虚假唤醒(spurious wakeup,但在现代JVM中很少见)。park 不需要处理 InterruptedException,并且它不会保持任何锁。
    • Thread.sleep():它会使当前线程休眠指定的时间,时间到了之后线程会变为就绪状态。sleep 期间线程不会释放任何锁,而且必须处理 InterruptedException87。
  2. LockSupport 的主要用途是什么?

    • LockSupport 主要用于创建锁和其他同步工具的基础类。它提供了一种机制,允许线程等待某个条件成立,而不需要轮询。这种等待是通过 park 和 unpark 方法来实现的,它们是构建高效锁和其他同步工具的关键87。
  3. LockSupport.park() 为什么比传统的线程等待方式更高效?

    • 传统的线程等待方式通常涉及到轮询(polling)或者使用 Thread.sleep(),这些方法都会浪费CPU资源,因为它们要么不断地检查条件,要么使线程进入睡眠状态,而在条件可能变为真时不会立即唤醒。LockSupport.park() 提供了一种更有效的方式,它允许线程在条件不满足时进入无消耗等待状态,直到它被 unpark 或中断,这样可以减少CPU的占用和上下文切换的开销87。
  4. LockSupport 是如何工作的?

    • LockSupport 中的 park 和 unpark 方法是通过底层的 Unsafe 类来实现的,这是一个提供低级别、非安全、操作系统级别访问的类。这些方法直接与JVM的线程调度器交互,将线程置于一种特殊的等待状态,在这种状态下线程不会消耗CPU资源,直到它被 unpark 或中断87。
  5. 使用 LockSupport 时需要注意什么?

    • 使用 LockSupport 时需要注意以下几点:

      1. park 方法可能会导致线程进入无限期等待,因此需要确保有相应的机制(如中断或 unpark)来唤醒线程。
      2. LockSupport 本身不提供锁或其他同步机制,它通常与其他同步原语(如 ReentrantLock)一起使用。
      3. 由于 LockSupport 是基于底层的 Unsafe 类实现的,因此在使用时需要谨慎,避免在不适当的上下文中使用它。
      4. 在多线程编程中,正确地处理中断和 InterruptedException 非常重要,尤其是在使用 LockSupport 时。即使 park 方法本身不会抛出 InterruptedException,但在使用它构建的同步工具中可能需要处理中断。
  6. LockSupport.unpark 能否唤醒一个没有被 park 的线程?

    • 不能。LockSupport.unpark 只对已经通过 LockSupport.park 阻塞的线程有效。如果一个线程没有被 parkunpark 对其没有效果。
  7. LockSupport.park 是否会响应中断?

    • 是的。如果一个线程在 park 状态时被中断,park 方法会立即返回,并且线程的中断状态将被清除。因此,通常建议在 park 之后检查中断状态。
  8. LockSupport 是否保证公平性?

    • 不保证。LockSupport 不保证按照 unpark 调用的顺序唤醒线程,它的唤醒操作是不可预测的。
  9. LockSupport.parkObject.wait 有何不同?

    • LockSupport.park 不需要获取对象的锁就能阻塞线程,而 Object.wait 必须在同步代码块或方法中调用,并且需要线程持有相应的监视器锁。此外,park 不会抛出 InterruptedException,而 wait 会。
  10. LockSupport 是否可以与其他锁机制一起使用?

    • 是的。LockSupport 可以与 java.util.concurrent.locks 包中的锁如 ReentrantLock 结合使用,以及可以用于实现自定义同步器和锁。
  11. 如何使用 LockSupport 实现线程间的协调?

    • 可以通过一个共享的协调对象或者信号量来实现。一个线程可以 park 直到协调对象的状态满足某个条件,而另一个线程在条件满足时 unpark 等待的线程。
  12. LockSupport 在 Java 并发包中扮演什么角色?

    • LockSupport 是 Java 并发包中的一个基础工具类,它提供了线程阻塞和唤醒的基本操作。它在实现更高级的并发组件如 SemaphoreCountDownLatch 以及其他自定义同步器时非常有用。
  13. 解释 LockSupport 中的许可(permit)的概念。

    • LockSupport 使用一个内部的许可(permit)机制来控制线程的阻塞和唤醒。每个线程有一个与之关联的许可状态,初始值为0。当线程调用 park 时,如果它的许可状态为0,它将阻塞;如果许可状态为1,它将消费这个许可,将其状态更新为0,并继续执行。unpark 方法会增加目标线程的许可状态到1,如果线程因为许可状态为0而阻塞,这将唤醒它。
  14. LockSupport.park 是否有可能发生伪唤醒(spurious wakeup)?

    • 是的,尽管在现代 JVM 中这种情况很少见,但理论上 LockSupport.park 可能会发生伪唤醒,即线程在没有调用 unpark 的情况下被唤醒。因此,使用 park 方法时通常会在一个循环中,以确保线程在条件不满足时能够重新阻塞。