LockSupport 类位于 java.util.concurrent.locks 包,它被用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport 类跟 Object 的 wait() 和 notify() 方法很像,也可以阻塞一个线程,然后又恢复一个线程;不过 LockSupport 有个比较大的区别就是,wait() 让线程阻塞前,必须要获取到同步锁。而 LockSupport 类则不用,可在线程内任意位置让线程阻塞。
例如:假如我们有两个线程 thread-1 和 thread-2,其中 thread-1 需要等待 thread-2 通知,然后继续进行工作。
(1)下面采用 wait() 和 notify() 方法实现,在调用对象的 wait() 或者 notify() 之前需要使用 synchronized 获取对象同步锁。代码如下:
public class LockSupportDemo1 { public static void main(String[] args) throws Exception { final Object obj = new Object(); new Thread(new Runnable() { public void run() { try { System.out.println(Thread.currentThread().getName()); // 等待对象 notify 或 notifyAll 通知 // 需要先获取到同步锁 synchronized (obj) { obj.wait(); } System.out.println(Thread.currentThread().getName() + ".....finished"); } catch (Exception e) { e.printStackTrace(); } } }, "thread-1").start(); new Thread(new Runnable() { public void run() { try { System.out.println(Thread.currentThread().getName()); Thread.sleep(2000); synchronized (obj) { obj.notify(); // 通知对象,可以继续干活了 } System.out.println(Thread.currentThread().getName() + ".....finished"); } catch (Exception e) { e.printStackTrace(); } } }, "thread-2").start(); } }
我们继续使用 LocKSupport 工具类来实现上面业务,代码如下:
public class LockSupportDemo2 { public static void main(String[] args) throws Exception { final Thread t1 = new Thread(new Runnable() { public void run() { try { System.out.println(Thread.currentThread().getName()); LockSupport.park(); // 等待,阻塞线程 System.out.println(Thread.currentThread().getName() + ".....finished"); } catch (Exception e) { e.printStackTrace(); } } }, "thread-1"); t1.start(); new Thread(new Runnable() { public void run() { try { System.out.println(Thread.currentThread().getName()); Thread.sleep(2000); LockSupport.unpark(t1); // 释放阻塞 System.out.println(Thread.currentThread().getName() + ".....finished"); } catch (Exception e) { e.printStackTrace(); } } }, "thread-2").start(); } }
程序输出结果如下:
thread-1 thread-2 thread-1.....finished thread-2.....finished
LockSupport 类以及每个使用它的线程与一个许可进行关联。如果该许可可用,并且可在进程中使用,则调用 park() 将立即返回;否则,可能阻塞。如果许可尚不可用,则可以调用 unpark() 使其可用。例如:
public class LockSupportDemo4 { public static void main(String[] args) throws Exception { final Thread t1 = new Thread(new Runnable() { public void run() { try { System.out.println(Thread.currentThread().getName() + ".....start"); System.out.println("t1 睡眠5秒"); Thread.sleep(5000); System.out.println("直接返回,不阻塞,因为线程的许可已经可用了"); LockSupport.park(); System.out.println(Thread.currentThread().getName() + ".....finished"); } catch (Exception e) { e.printStackTrace(); } } }, "thread-1"); t1.start(); // 提前使用 unpark() 方法让 t1 中的许可可用 LockSupport.unpark(t1); System.out.println("调用了 LockSupport.unpark(t1)"); } }
因此,unpark() 在 park() 前面还是后面调用没有影响。
park() 和 unpark() 方法提供了阻塞和解除阻塞线程的有效方法,并且不会遇到导致过时(Deprecated)方法 Thread.suspend() 和 Thread.resume() 因为以下目的变得不可用的问题:
由于许可的存在,调用 park 的线程和另一个试图将其 unpark 的线程之间的竞争将保持活性。此外,如果调用者线程被中断,并且支持超时,则 park 将返回。
park 方法还可以在其他任何时间 “毫无理由” 地返回,因此通常必须在重新检查返回条件的循环里调用此方法。从这个意义上说,park 是 “忙碌等待” 的一种优化,它不会浪费这么多的时间进行自旋,但是必须将它与 unpark 配对使用才更高效。
三种形式的 park 还各自支持一个 blocker 对象参数。此对象在线程受阻塞时被记录,以允许监视工具和诊断工具确定线程受阻塞的原因。方法定义如下:
public static void park(Object blocker)
其中,blocker 是导致此线程暂停的同步对象(方便我们分析阻塞原因)。例如:
public class LockSupportDemo3 { public static void main(String[] args) throws Exception { new LockSupportDemo3(); } public LockSupportDemo3() { Thread t1 = new Thread(new Runnable() { public void run() { try { System.out.println(Thread.currentThread().getName()); LockSupport.park(LockSupportDemo3.this); System.out.println(Thread.currentThread().getName() + ".....finished"); } catch (Exception e) { e.printStackTrace(); } } }, "thread-1"); t1.start(); } }
上面代码中,使用 LockSupport.park(LockSupportDemo3.this) 为 park 指定一个 blocker。
使用 Java VisualVM 工具获取当前线程的 threaddump 数据,你会发现如下信息:
"thread-1" #11 prio=5 os_prio=0 tid=0x000000001ddff000 nid=0x178c waiting on condition [0x000000001e72f000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x000000076c309740> (a com.huangx.thread_utils.LockSupportDemo3) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at com.huangx.thread_utils.LockSupportDemo3$1.run(LockSupportDemo3.java:21) at java.lang.Thread.run(Thread.java:745)
导致该线程 WAITING(parking)的对象是 com.huangx.thread_utils.LockSupportDemo3。如下图:
这些方法被设计用来作为创建高级同步实用工具的工具,对于大多数并发控制应用程序而言,它们本身并不是很有用。park 方法仅设计用于以下形式的构造:
while (!canProceed()) { // ... LockSupport.park(this); }
在这里,在调用 park 之前,canProceed 和其他任何动作都不会锁定或阻塞。因为每个线程只与一个许可关联,park 的任何中间使用都可能干扰其预期效果。
示例用法:以下是一个先进先出 (first-in-first-out) 非重入锁类的框架,如下:
public class FIFOMutex { private final AtomicBoolean locked = new AtomicBoolean(false); private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>(); public void lock() { boolean wasInterrupted = false; Thread current = Thread.currentThread(); waiters.add(current); // Block while not first in queue or cannot acquire lock while (waiters.peek() != current || !locked.compareAndSet(false, true)) { LockSupport.park(this); // ignore interrupts while waiting if (Thread.interrupted()) wasInterrupted = true; } waiters.remove(); // reassert interrupt status on exit if (wasInterrupted) current.interrupt(); } public void unlock() { locked.set(false); LockSupport.unpark(waiters.peek()); } }