首页 > 其他分享 >LockSupport

LockSupport

时间:2024-07-27 15:20:04浏览次数:5  
标签:Thread park LockSupport 阻塞 unpark 线程

LockSupprot 用来阻塞和唤醒线程,底层实现依赖于 Unsafe 类(后面会细讲)。

该类包含一组用于阻塞和唤醒线程的静态方法,这些方法主要是围绕 park 和 unpark 展开。

public class Main {
    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();

        // 当 counterThread 数到 10 时,它会唤醒 mainThread。而 mainThread 在调用 park 方法时会被阻塞,直到被 unpark。
        Thread counterThread = new Thread(() -> {
            for (int i = 1; i <= 20; i++) {
                System.out.println(i);
                if (i == 10) {
                    // 当数到10时,唤醒主线程
                    LockSupport.unpark(mainThread);
                }
            }
        });
        counterThread.start();

        // 主线程调用park
        LockSupport.park();
        System.out.println("Main thread was unparked.");
    }
}

阻塞线程


  1. void park():阻塞当前线程,如果调用 unpark 方法或线程被中断,则该线程将变得可运行。请注意,park 不会抛出 InterruptedException,因此线程必须单独检查其中断状态。
  2. void park(Object blocker):功能同方法 1,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。
  3. void parkNanos(long nanos):阻塞当前线程一定的纳秒时间,或直到被 unpark 调用,或线程被中断。
  4. void parkNanos(Object blocker, long nanos):功能同方法 3,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。
  5. void parkUntil(long deadline):阻塞当前线程直到某个指定的截止时间(以毫秒为单位),或直到被 unpark 调用,或线程被中断。
  6. void parkUntil(Object blocker, long deadline):功能同方法 5,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。

唤醒线程


void unpark(Thread thread):唤醒一个由 park 方法阻塞的线程。如果该线程未被阻塞,那么下一次调用 park 时将立即返回。这允许“先发制人”式的唤醒机制。

实际上,LockSupport 阻塞和唤醒线程的功能依赖于 sun.misc.Unsafe,比如 LockSupport 的 park 方法是通过 unsafe.park() 方法实现的。

Dump 线程


"Dump 线程"通常是指获取线程的当前状态和调用堆栈的详细快照。这可以提供关于线程正在执行什么操作以及线程在代码的哪个部分的重要信息。

下面是线程转储中可能包括的一些信息:

  • 线程 ID 和名称:线程的唯一标识符和可读名称。
  • 线程状态:线程的当前状态,例如运行(RUNNABLE)、等待(WAITING)、睡眠(TIMED_WAITING)或阻塞(BLOCKED)。
  • 调用堆栈:线程的调用堆栈跟踪,显示线程从当前执行点回溯到初始调用的完整方法调用序列。
  • 锁信息:如果线程正在等待或持有锁,线程转储通常还包括有关这些锁的信息。

线程转储可以通过各种方式获得,例如使用 Java 的 jstack 工具,或从 Java VisualVM、Java Mission Control 等工具获取。

下面是一个简单的例子,通过 LockSupport 阻塞线程,然后通过 Intellij IDEA 查看 dump 线程信息。

public class LockSupportDemo {
    public static void main(String[] args) {
        LockSupport.park();
    }
}

先运行程序,再在 Run 面板中找到 attach to process,选择 attach 到主线程:

再到 debugger 面板中找到 export threads。

导出后就能看见线程信息了。

与 synchronized 的区别


synchronized 会使线程阻塞,线程会进入 BLOCKED 状态,而调用 LockSupprt 方法阻塞线程会使线程进入到 WAITING 状态。

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Thread is parked now");
            LockSupport.park();
            System.out.println("Thread is unparked now");
        });
        thread.start();

        try {
            Thread.sleep(3000); // 主线程等待3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        LockSupport.unpark(thread); // 主线程唤醒阻塞的线程
    }
}

设计思路


LockSupport 会为使用它的线程关联一个许可证(permit)状态,permit 的语义「是否拥有许可」,0 代表否,1 代表是,默认是 0。

  • LockSupport.unpark:指定线程关联的 permit 直接更新为 1,如果更新前的permit<1,唤醒指定线程
  • LockSupport.park:当前线程关联的 permit 如果>0,直接把 permit 更新为 0,否则阻塞当前线程

  • 线程 A 执行LockSupport.park,发现 permit 为 0,未持有许可证,阻塞线程 A
  • 线程 B 执行LockSupport.unpark(入参线程 A),为 A 线程设置许可证,permit 更新为 1,唤醒线程 A
  • 线程 B 流程结束
  • 线程 A 被唤醒,发现 permit 为 1,消费许可证,permit 更新为 0
  • 线程 A 执行临界区
  • 线程 A 流程结束

经过上面的分析得出结论 unpark 的语义明确为「使线程持有许可证」,park 的语义明确为「消费线程持有的许可」,所以 unpark 与 park 的执行顺序没有强制要求,只要控制好使用的线程即可,unpark=>park执行流程如下:

  • permit 默认是 0,线程 A 执行 LockSupport.unpark,permit 更新为 1,线程 A 持有许可证
  • 线程 A 执行 LockSupport.park,此时 permit 是 1,消费许可证,permit 更新为 0
  • 执行临界区
  • 流程结束

因 park 阻塞的线程不仅仅会被 unpark 唤醒,还可能会被线程中断(Thread.interrupt)唤醒,而且不会抛出 InterruptedException 异常,所以建议在 park 后自行判断线程中断状态,来做对应的业务处理。

为什么推荐使用 LockSupport 来做线程的阻塞与唤醒(线程间协同工作),因为它具备如下优点:

  • 以线程为操作对象更符合阻塞线程的直观语义
  • 操作更精准,可以准确地唤醒某一个线程(notify 随机唤醒一个线程,notifyAll 唤醒所有等待的线程)
  • 无需竞争锁对象(以线程作为操作对象),不会因竞争锁对象产生死锁问题
  • unpark 与 park 没有严格的执行顺序,不会因执行顺序引起死锁问题,比如「Thread.suspend 和 Thread.resume」没按照严格顺序执行,就会产生死锁

面试题


有 3 个独立的线程,一个只会输出 A,一个只会输出 B,一个只会输出 C,在三个线程启动的情况下,请用合理的方式让他们按顺序打印 ABCABC。

public class Main {
    private static Thread t1, t2, t3;

    public static void main(String[] args) {
        t1 = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                LockSupport.park();
                System.out.print("A");
                LockSupport.unpark(t2);
            }
        });

        t2 = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                LockSupport.park();
                System.out.print("B");
                LockSupport.unpark(t3);
            }
        });

        t3 = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                LockSupport.park();
                System.out.print("C");
                LockSupport.unpark(t1);
            }
        });

        t1.start();
        t2.start();
        t3.start();

        // 主线程稍微等待一下,确保其他线程已经启动并且进入park状态。
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 启动整个流程
        LockSupport.unpark(t1);
    }
}

LockSupport 提供了一种更底层和灵活的线程调度方式。它不依赖于同步块或特定的锁对象。可以用于构建更复杂的同步结构,例如自定义锁或并发容器。LockSupport.park 与 LockSupport.unpark 的组合使得线程之间的精确控制变得更容易,而不需要复杂的同步逻辑和对象监视。

标签:Thread,park,LockSupport,阻塞,unpark,线程
From: https://www.cnblogs.com/sprinining/p/18326997

相关文章

  • JUC工具(LockSupport)
    LockSupport用来创建锁和其他同步类的基本线程阻塞LockSupport用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行LockSuppo......
  • 【JDK】LockSupport 工具类
    1 前言LockSupport工具类最近复习到这个类了,之前也没做笔记,这里简单回顾下哈。JDK中的rt.jar包里面的LockSupport是个工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用L......
  • 【Java 并发编程】LockSupport
    目录简介方法介绍阻塞和唤醒示例示例1示例2示例3阻塞对象blocker的作用和显式锁、隐式锁等待唤醒的区别简介java.util.concurrent.locks.LockSupport是一个工具类,提供了基本的线程阻塞和唤醒功能,它是创建锁和其他同步组件的基础工具,内部是基于sun.misc.Unsafe类实现的。Lo......
  • 20230608 java.util.concurrent.locks.LockSupport
    介绍java.util.concurrent.locks.LockSupportpublicclassLockSupportAPIstaticsetCurrentBlockervoidsetCurrentBlocker(Objectblocker)设置当前线程的阻塞对象getBlockerObjectgetBlocker(Threadt)返回提供给最近一次调用尚未解除阻塞的park方法的阻塞......
  • 一文读懂LockSupport
    阅读本文前,需要储备的知识点如下,点击链接直接跳转。java线程详解Java不能操作内存?Unsafe了解一下LockSupport介绍搞java开发的基本都知道J.U.C并发包(即java.util.concurrent包),所有并发相关的类基本都来自于这个包下,这个包是JDK1.5以后由祖师爷DougLea写的,LockSupport也是在这......
  • 忘记LockSupport怎么用了?那我们举个有趣的小例子,永远记住它!
    概述LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,它弥补了由于resume()在前发生,导致线程无法继续执行的情况。和Object.wait()方法相比,它不需要先获得某个对象的锁,也不会抛出InterruptedException异常。park()可以阻塞......
  • LockSupport与线程中断
    1LockSupport是什么LockSupport是用来创建锁和其他同步类的基本阻塞原语。park方法可以阻塞线程,unpark方法可以唤醒线程。与object的wait和notify不同的是,不需要synchonized的限制,没有先后顺序的限制。2线程中断线程不应该被其他线程中断,应该自己中断。线程的三个中断方法......
  • 四、JUC-LockSupport与线程中断
    一、线程中断机制1、概述一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,所以,Thread.stop、Thread.suspend、Thread.resume都已经被废弃了在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于......
  • JUC并发编程基础篇第六章之LockSupport[notify,signal之外的另一种唤醒方式]
    目录1、LockSupport有什么用2、使用wait和notify唤醒一个线程2.1、正常情况2.2、异常情况2,这里去掉了synchronized(o){}代码块2.3、异常情况3先notify再wait3、使用await和signal唤醒一个线程3.1、正常情况3.2、异常情况:如果去除锁块3.3、异常情况:先执行signal......
  • 【打怪升级】【juc】关于LockSupport
    通过juc下LockSupport,可以达到阻塞和唤醒线程的操作 LockSupportLockSupport是juc下一个线程阻塞唤醒的工具类。它的主要方法有:每个使用LockSupport的线程,会有一个许可;调用park会立即返回,否则会被阻塞。如果许可不可用,则可以调用unpark供其可使用,......