LockSupport 类

本文将介绍 Java 并发包中的线程阻塞工具类 LockSupport。

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。如下图:

LockSupport 类

这些方法被设计用来作为创建高级同步实用工具的工具,对于大多数并发控制应用程序而言,它们本身并不是很有用。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());
    }
}
生活总会给你答案的,但不会马上把一切都告诉你。只要你肯等一等,生活的美好,总在你不经意的时候,盛装莅临。
1 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号