LockSupport秘籍:新手入门,高手精通,玩转同步控制

简介: LockSupport秘籍:新手入门,高手精通,玩转同步控制

一、LockSupport是什么

java.util.concurrent.locks.LockSupport 是 Java 并发编程中的一个非常有用的线程阻塞工具类,它包含可以阻塞和唤醒线程的方法。这个类是Java并发编程中的基础工具之一,通常用于构建锁或其他同步组件。LockSupport的所有方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。


LockSupport中的park()和unpark()方法分别用于阻塞线程和解除线程的阻塞状态。调用park()方法后,当前线程会被挂起,直到它被其他线程通过unpark()方法唤醒,或者线程被中断,或者调用该方法的线程从调用park()方法开始已经过了一个不可预知的时间。需要注意的是,LockSupport不会释放任何锁资源,因此在调用park()之前应确保当前线程没有持有任何可能导致死锁的锁。


LockSupport实际上是对线程等待/通知机制(即Object类中的wait/notify/notifyAll方法)的一种改进和扩展。与传统的等待/通知机制相比,LockSupport提供了更精确的线程阻塞和唤醒控制,以及更好的可移植性和可维护性。此外,LockSupport还可以与Java的并发锁(如ReentrantLock)和条件变量(如Condition)等高级并发工具结合使用,以实现更复杂的线程同步和协作模式。


LockSupport提供了一种挂起和恢复线程的机制,通常与线程同步原语(如锁和条件)一起使用。但是,与 Object.wait() 和 Object.notify() 或 Object.notifyAll() 相比,LockSupport 提供了一种更灵活的线程挂起和恢复方法。

二、主要方法

  1. LockSupport.park()
  • 调用此方法会导致当前线程被挂起(或阻塞),直到它被 unpark,线程被中断,或者调用该方法的线程从调用 park 方法开始已经过了一个不可预知的时间。
  • 它是一个非阻塞性的挂起操作,不会释放任何锁资源。因此,通常与 java.util.concurrent.locks 包中的锁一起使用。
  1. LockSupport.unpark(Thread thread)
  • 此方法用于解除线程的挂起状态。如果线程没有被挂起,调用此方法没有效果。
  • 只有之前通过 LockSupport.park() 挂起的线程才能被 unpark() 恢复。

三、 使用场景

  • 自定义锁实现:当你需要实现自己的锁或同步原语时,可以使用 LockSupport 来挂起和恢复线程。这比使用内置的等待/通知机制更加灵活和高效。
  • java.util.concurrent.locks 一起使用:这个包中的锁(如 ReentrantLock)和条件(如 Condition)通常与 LockSupport 一起使用来实现复杂的线程同步模式。
  • 实现响应式编程模型:在某些响应式编程场景中,线程可能需要等待某个事件或条件发生。在这种情况下,可以使用 LockSupport 来挂起线程,直到事件或条件满足。

四、LockSupport的使用

下面代码使用 LockSupport 来挂起和恢复线程:

import java.util.concurrent.locks.LockSupport;

public class LockSupportExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始运行...");
            // 做一些工作...
            
            System.out.println("线程挂起...");
            LockSupport.park(); // 挂起线程
            
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("线程被中断...");
            } else {
                System.out.println("线程被恢复...");
            }
            
            // 继续执行其他任务...
        });
        
        thread.start();
        // 让主线程睡眠一段时间,确保子线程已经挂起
        Thread.sleep(2000);
        System.out.println("尝试恢复线程...");
        LockSupport.unpark(thread); // 恢复线程
        // 让主线程再睡眠一段时间,确保子线程有机会执行完毕
        Thread.sleep(2000);
    }
}

五、三种方法让线程等待和唤醒

让线程等待和唤醒是并发编程中的常见需求。以下是三种常用的方法,分别是使用Object类的wait()/notify()/notifyAll()方法、使用Lock和Condition接口,以及使用LockSupport类的park()和unpark()方法。

1. 使用Object类的wait()/notify()/notifyAll()方法

这是Java中最早提供的线程等待和唤醒机制。wait()方法会使当前线程等待,直到其他线程调用同一个对象的notify()notifyAll()方法。

public class WaitNotifyExample {
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("线程1等待");
                    lock.wait(); // 线程等待
                    System.out.println("线程1被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2唤醒其他线程");
                lock.notify(); // 唤醒等待的线程
            }
        });

        thread1.start();
        Thread.sleep(1000); // 确保thread1先执行
        thread2.start();
    }
}

2. 使用LockCondition接口

java.util.concurrent.locks.Lock接口和它的实现类(如ReentrantLock)提供了比synchronized更灵活的锁机制。Condition接口与Lock一起使用,可以实现等待/通知模式。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockConditionExample {
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程1等待");
                condition.await(); // 线程等待
                System.out.println("线程1被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread thread2 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("线程2唤醒其他线程");
                condition.signal(); // 唤醒等待的线程
            } finally {
                lock.unlock();
            }
        });

        thread1.start();
        Thread.sleep(1000); // 确保thread1先执行
        thread2.start();
    }
}

3. 使用LockSupport类的park()unpark()方法

LockSupport它可以在线程中的任何位置阻塞线程的执行。park()方法用于阻塞线程,unpark()方法用于解除线程的阻塞状态。

import java.util.concurrent.locks.LockSupport;

public class LockSupportExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            System.out.println("线程1开始运行");
            LockSupport.park("等待唤醒"); // 线程等待
            System.out.println("线程1被唤醒");
        });

        thread1.start();
        Thread.sleep(1000); // 确保thread1先执行并等待

        System.out.println("主线程唤醒线程1");
        LockSupport.unpark(thread1); // 唤醒线程1
    }
}

在使用LockSupport.park()时,建议检查线程的中断状态,因为park()不会响应中断,除非显式地检查并处理中断。在实际应用中,可以将park()放在一个循环中,并在循环条件中检查中断状态。

六、LockSupport源码分析

实际上 LockSupport 类的实现非常简单,并且它的很多功能都是依赖于 JVM 的底层支持的。它的核心方法是 parkunpark,分别用于阻塞和解除阻塞线程。

下面是 LockSupport 类的一些关键部分的源码分析:

public class LockSupport {
    private LockSupport() {} // Cannot be instantiated.

    // Blocks until unparked, interrupted, or the caller spuriously returns.
    public static void park() {
        Unsafe.getUnsafe().park(false, 0L);
    }

    // Blocks until unparked, interrupted, the caller spuriously returns,
    // or the deadline passes, whichever comes first.
    public static void parkNanos(long nanos) {
        if (nanos > 0)
            Unsafe.getUnsafe().park(false, nanos);
    }

    // Blocks until unparked, interrupted, the caller spuriously returns,
    // or approximately deadline milliseconds have passed, whichever comes first.
    public static void parkUntil(long deadline) {
        Unsafe.getUnsafe().park(true, deadline);
    }

    // Unblocks the thread blocked by park().
    public static void unpark(Thread thread) {
        if (thread != null)
            Unsafe.getUnsafe().unpark(thread);
    }

    // Hotspot implementation of park/unpark may need some help to find
    // threads blocked on the LockSupport objects created by this class.
    // These methods are natively implemented in the JDK.
    private static native void setBlocker(Thread t, Object arg);
    private static native Object getBlocker(Thread t);
    
    // Disables the current thread for thread scheduling purposes, for up to the
    // specified waiting time, unless the `permit` is available.
    // This method is designed to be used as a tool for creating higher-level
    // synchronization utilities, and is not intended for general use.
    // It supports a single permit, which is initially unavailable, and
    // which becomes available when `unpark` is invoked, or if the caller
    // spuriously returns.
    // ... additional methods related to permit (not shown here)
}

从上面的源码片段中,我们可以看到以下几点:

  1. LockSupport 是一个工具类,不能被实例化(构造函数是私有的)。
  2. 它提供了三个 park 方法和一个 unpark 方法。这些方法都是静态的,可以直接通过类名调用。
  3. park 方法用于阻塞当前线程,直到它被 unpark,线程被中断,或者出现其他未指定的情况(即“spurious wakeup”,虽然在现代 JVM 中这种情况很少见)。
  4. parkNanosparkUntil 方法允许设置一个时间限制,在此时间之后线程会自动解除阻塞,即使没有被其他线程显式地 unpark
  5. unpark 方法用于解除指定线程的阻塞状态。如果线程没有被阻塞,或者由于某种原因已经解除阻塞,调用 unpark 不会有任何效果。
  6. 底层的阻塞和解除阻塞操作是通过 Unsafe 类来实现的,这是一个提供低级别、非安全、操作系统级别访问的类。Unsafe 类中的 parkunpark 方法是本地方法(通过 JNI 调用),它们直接与 JVM 的线程调度器交互。
  7. setBlocker 和 getBlocker 方法是本地方法,用于在 JVM 内部跟踪由 LockSupport 阻塞的线程。这些方法通常不用于应用程序代码。

七、 注意事项

  • 当使用 LockSupport.park() 挂起线程时,应该确保有一个明确的机制来恢复(通过 unpark())或中断线程,以避免线程永久挂起。
  • 由于 LockSupport 不会释放任何锁资源,因此在调用 park() 之前应该确保线程没有持有任何可能导致死锁的锁。
  • 与 Object.wait() 和 Thread.sleep() 不同,LockSupport.park() 不会响应中断,除非你在调用 park() 之前或之后显式检查中断状态。但是,如果线程在 park() 期间被中断,那么从 park() 返回后,中断状态将被清除(设置为 false),除非你在此之前调用了 Thread.interrupted() 来检查并清除中断状态。因此,通常建议在调用 park() 的循环中检查中断状态。

八、关于 LockSupport 的一些常见面试题

面试题1:LockSupport.park()Thread.sleep() 有什么区别?

答案

LockSupport.park():它会暂停当前线程的执行,直到它被 unpark,线程被中断,或者出现虚假唤醒(spurious wakeup,但在现代JVM中很少见)。park 不需要处理 InterruptedException,并且它不会保持任何锁。

Thread.sleep():它会使当前线程休眠指定的时间,时间到了之后线程会变为就绪状态。sleep 期间线程不会释放任何锁,而且必须处理 InterruptedException。

面试题2:LockSupport 的主要用途是什么?

答案

LockSupport 主要用于创建锁和其他同步工具的基础类。它提供了一种机制,允许线程等待某个条件成立,而不需要轮询。这种等待是通过 parkunpark 方法来实现的,它们是构建高效锁和其他同步工具的关键。

面试题3:LockSupport.park() 为什么比传统的线程等待方式更高效?

答案

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

面试题4:LockSupport 是如何工作的?

答案

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


面试题5:使用 LockSupport 时需要注意什么?

答案

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

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

九、结语

LockSupport 提供了 park 和 unpark 方法,用于阻塞和解除阻塞线程,是构建锁和其他同步工具的基础。与传统的 Thread.sleep() 或Thread.yield() 不同,LockSupport 不需要线程处理 InterruptedException,而且它不会保持任何锁,因此在构建高效、响应式的并发系统时特别有用。


LockSupport.park() 方法允许线程等待某个条件成立,而不会消耗CPU资源,直到它被其他线程 unpark 或被中断。这种等待方式比轮询或使用 sleep() 方法更高效,因为它减少了CPU的占用和上下文切换的开销。


但lockSupport 本身不提供锁或其他同步机制,它通常与其他同步原语(如 ReentrantLock)一起使用,以实现更复杂的同步需求。此外,在使用 LockSupport 时,需要确保有相应的机制来唤醒等待的线程,避免线程进入无限期等待。


相关文章
|
6月前
|
NoSQL Java 关系型数据库
凭借Java开发进阶面试秘籍(核心版)逆流而上
最近参加了面试或者身边有朋友在面试的兄弟有没有发现,现在的面试不仅会问八股文,还会考察框架、项目实战、算法数据结构等等,需要准备的越来越多。 其实面试的时候,并不是要求你所有的知识点都会,而是关键的问题答到点子上!这份《Java 开发进阶面试秘籍(核心版)》由 P8 面试官整体把控,目前已经更新了 30 万字! 资料中涵盖了一线大厂、中小厂面试真题,毕竟真题都是技术领域最经典的基础知识和经验沉淀的汇总,非常有必要学习掌握!双重 buff 叠加,offer 接到手软~ 点击此处取,这可能是你到目前为止领取的最具含金量的一份资料! 整套资料涵盖:Spring、Spring
|
2月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
35 7
|
3月前
|
Java 程序员
从0到1,手把手教你玩转Java多线程同步!
从0到1,手把手教你玩转Java多线程同步!
39 3
|
6月前
|
存储 算法 C语言
【C 言专栏】用 C 语言开发游戏的实践
【5月更文挑战第5天】本文探讨了使用C语言开发游戏的实践,包括选择适合的游戏类型(如贪吃蛇、俄罗斯方块),设计游戏框架、图形界面和逻辑,以及音效添加。文章还强调了性能优化、测试调试、跨平台挑战及未来发展趋势。对于热衷于C语言的开发者,这是一次挑战与乐趣并存的探索之旅。
207 0
【C 言专栏】用 C 语言开发游戏的实践
|
安全 算法 Java
杰哥教你面试之一百问系列:java多线程
java多线程是java面试中的高频问题,如何才能在面试中脱颖而出呢?熟读这里的一百个java多线程面试问题即可。
|
存储 安全 Java
Java多线程超详细笔记
Java多线程超详细笔记
67 1
|
安全 Java
JAVA多线程初学者详解
JAVA多线程初学者详解
|
安全 Java 调度
java多线程使用教程
java多线程使用教程
120 0
|
Java
JAVA练手小游戏——多线程知识点小练习——文字版勇者斗恶龙
JAVA练手小游戏——多线程知识点小练习——文字版勇者斗恶龙
133 0
|
XML Java 关系型数据库
Java多线程-手把手Java多线程实战
通过一个样例的运行,量化指标,来对Java的多线程优点进行解释,并且也带入问题解决思路
113 0
Java多线程-手把手Java多线程实战