一、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 提供了一种更灵活的线程挂起和恢复方法。
二、主要方法
LockSupport.park()
- 调用此方法会导致当前线程被挂起(或阻塞),直到它被
unpark
,线程被中断,或者调用该方法的线程从调用park
方法开始已经过了一个不可预知的时间。 - 它是一个非阻塞性的挂起操作,不会释放任何锁资源。因此,通常与
java.util.concurrent.locks
包中的锁一起使用。
- 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. 使用Lock
和Condition
接口
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 的底层支持的。它的核心方法是 park
和 unpark
,分别用于阻塞和解除阻塞线程。
下面是 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) }
从上面的源码片段中,我们可以看到以下几点:
LockSupport
是一个工具类,不能被实例化(构造函数是私有的)。- 它提供了三个
park
方法和一个unpark
方法。这些方法都是静态的,可以直接通过类名调用。 park
方法用于阻塞当前线程,直到它被unpark
,线程被中断,或者出现其他未指定的情况(即“spurious wakeup”,虽然在现代 JVM 中这种情况很少见)。parkNanos
和parkUntil
方法允许设置一个时间限制,在此时间之后线程会自动解除阻塞,即使没有被其他线程显式地unpark
。unpark
方法用于解除指定线程的阻塞状态。如果线程没有被阻塞,或者由于某种原因已经解除阻塞,调用unpark
不会有任何效果。- 底层的阻塞和解除阻塞操作是通过
Unsafe
类来实现的,这是一个提供低级别、非安全、操作系统级别访问的类。Unsafe
类中的park
和unpark
方法是本地方法(通过 JNI 调用),它们直接与 JVM 的线程调度器交互。 - 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
主要用于创建锁和其他同步工具的基础类。它提供了一种机制,允许线程等待某个条件成立,而不需要轮询。这种等待是通过 park
和 unpark
方法来实现的,它们是构建高效锁和其他同步工具的关键。
面试题3:LockSupport.park()
为什么比传统的线程等待方式更高效?
答案:
传统的线程等待方式通常涉及到轮询(polling)或者使用 Thread.sleep(),这些方法都会浪费CPU资源,因为它们要么不断地检查条件,要么使线程进入睡眠状态,而在条件可能变为真时不会立即唤醒。LockSupport.park() 提供了一种更有效的方式,它允许线程在条件不满足时进入无消耗等待状态,直到它被 unpark 或中断,这样可以减少CPU的占用和上下文切换的开销。
面试题4:LockSupport
是如何工作的?
答案:
LockSupport 中的 park 和 unpark 方法是通过底层的 Unsafe 类来实现的,这是一个提供低级别、非安全、操作系统级别访问的类。这些方法直接与JVM的线程调度器交互,将线程置于一种特殊的等待状态,在这种状态下线程不会消耗CPU资源,直到它被 unpark 或中断。
面试题5:使用 LockSupport
时需要注意什么?
答案:
使用 LockSupport
时需要注意以下几点:
park
方法可能会导致线程进入无限期等待,因此需要确保有相应的机制(如中断或unpark
)来唤醒线程。LockSupport
本身不提供锁或其他同步机制,它通常与其他同步原语(如ReentrantLock
)一起使用。- 由于
LockSupport
是基于底层的Unsafe
类实现的,因此在使用时需要谨慎,避免在不适当的上下文中使用它。 - 在多线程编程中,正确地处理中断和
InterruptedException
非常重要,尤其是在使用LockSupport
时。即使park
方法本身不会抛出InterruptedException
,但在使用它构建的同步工具中可能需要处理中断。
九、结语
LockSupport 提供了 park 和 unpark 方法,用于阻塞和解除阻塞线程,是构建锁和其他同步工具的基础。与传统的 Thread.sleep() 或Thread.yield() 不同,LockSupport 不需要线程处理 InterruptedException,而且它不会保持任何锁,因此在构建高效、响应式的并发系统时特别有用。
LockSupport.park() 方法允许线程等待某个条件成立,而不会消耗CPU资源,直到它被其他线程 unpark 或被中断。这种等待方式比轮询或使用 sleep() 方法更高效,因为它减少了CPU的占用和上下文切换的开销。
但lockSupport 本身不提供锁或其他同步机制,它通常与其他同步原语(如 ReentrantLock)一起使用,以实现更复杂的同步需求。此外,在使用 LockSupport 时,需要确保有相应的机制来唤醒等待的线程,避免线程进入无限期等待。