【并发编程】LockSupport源码详解

news2024/11/26 9:06:10

目录

一、前言

1.1 简介

1.2 为什么说LockSupport是Java并发的基石?

二、LockSupport的用途

2.1 LockSupport的主要方法

2.2 使用案例

2.3 总结

三、LockSupport 源码分析

3.1 学习原理前的前置知识

3.1.1 Unsafe.park()和Unsafe.unpark()

3.1.2wait和notify/notifyAll

3.1.3 LockSupport灵活性

3.2 LockSupport中的主要成员及其加载时的初始化

3.2.1 parkBlockerOffset

3.2.2 SEED, PROBE, SECONDARY

3.3 构造方法

3.4 park方法

3.5 parkNanos 方法

3.6 parkUntil 方法

3.7 unpark 方法

3.8 LockSupport原理总结

四、中断响应


一、前言

1.1 简介

LockSupport是concurrent包中的一个线程阻塞工具类,所有的方法都是静态方法,不提供构造,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。

LockSupport用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用 LockSupport.park()时,表示当前线程将会等待,直至获得许可,当调用 LockSupport.unpark()时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。

1.2 为什么说LockSupportJava并发的基石?

当需要阻塞或唤醒一个线程的时候,JVM都会使用LockSupport工具类来完成相应工作。LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也被称为构建同步组件的基础工具

Java并发组件和并发工具类如下:

  • 并发组件:线程池、阻塞队列、Future和FutureTask、Lock和Condition。
  • 并发工具:CountDownLatch、CyclicBarrier、Semaphore和Exchanger。

并发组件和并发工具大都是基于AQS来实现的:

队列同步器AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。

而AQS中的控制线程又是通过LockSupport类来实现的,因此可以说,LockSupport是Java并发基础组件中的基础组件。LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。

二、LockSupport的用途

2.1 LockSupport的主要方法

接下面我来看看LockSupport有哪些常用的方法。主要有两类方法:park(阻塞线程)和unpark(解除阻塞)。

public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复当前线程
public static Object getBlocker(Thread t); // 获取线程的Blocker对象

为什么叫park呢,park英文意思为停车。我们如果把Thread看成一辆车的话,park就是让车停下,unpark就是让车启动然后跑起来。

2.2 使用案例

我们写一个例子来看看这个工具类怎么用的。

public class LockSupportDemo {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super(name);
        }
        @Override public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("被中断了");
                }
                System.out.println("继续执行");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(1000L);
        t2.start();
        Thread.sleep(3000L);
        t1.interrupt();
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

运行的结果如下:

这儿park和unpark其实实现了wait和notify的功能,不过还是有一些差别的。

  1. park不需要获取某个对象的锁。
  2. 因为中断的时候park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,然后做额外的处理。

我们再来看看Object blocker对象,这是个什么东西呢?这其实就是方便在线程dump的时候看到具体的阻塞对象的信息。

"t1" #10 prio=5 os_prio=31 tid=0x00007f95030cc800 nid=0x4e03 waiting on condition [0x00007000011c9000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
    // `下面的这个信息`
    at com.wtuoblist.beyond.concurrent.demo.chapter3.LockSupportDemo$ChangeObjectThread.run(LockSupportDemo.java:23) // 
    - locked <0x0000000795830950> (a java.lang.Object)

 blocker对象通过LockSupport.getBlocker方法获得。blocker对象只有在线程阻塞的时候才会被赋值,blocker对象是Thread线程类中的成员属性。

还有一个地方需要注意,相对于线程的stop和resume,park和unpark的先后顺序并不是那么严格。stop和resume如果顺序反了,会出现死锁现象。而park和unpark却不会。这又是为什么呢?还是看一个例子

public class LockSupportDemo {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super(name);
        }
        @Override public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                LockSupport.park();
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("被中断了");
                }
                System.out.println("继续执行");
            }
        }
    }
    public static void main(String[] args) {
        t1.start();
        LockSupport.unpark(t1);
        System.out.println("unpark invoked");
    }
}

t1内部有休眠1s的操作,所以unpark肯定先于park的调用,但是t1最终仍然可以完结。这是因为park和unpark会对每个线程维持一个许可(boolean值)

  1. unpark调用时,如果当前线程还未进入park,则许可为true,并且不会去执行unpark。
  2. park调用时,判断许可是否为true,如果是true,则继续往下执行;如果是false,则等待,直到许可为true。

意思就是如果新执行unpark,如果发现当前线程还没有执行park呢,那么unpark就会停在那等待,等到真的去执行了park之后,才会继续向下执行unpark。这个功能是通过两个方法共同维护的一个boolean类型的许可变量实现的。

我们再看看jdk的文档描述

2.3 总结

  • park和unpark可以实现类似wait和notify的功能,但是并不和wait和notify交叉,也就是说unpark不会对wait起作用,notify也不会对park起作用。
  • park和unpark的使用不会出现死锁的情况.
  • blocker的作用是在dump线程的时候看到阻塞对象的信息。

三、LockSupport 源码分析

3.1 学习原理前的前置知识

3.1.1 Unsafe.park()Unsafe.unpark()

在分析 LockSupport函数之前,先引入 sun.misc.Unsafe类中的 park和 unpark函数,因为 LockSupport的核心函数都是基于Unsafe类中定义的 park和 unpark函数,下面给出两个函数的定义:

public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);

 对两个函数的说明如下:

  • Unsafe.park函数:阻塞线程,并且该线程在下列情况发生之前都会被阻塞:① 调用 unpark函数,释放该线程的许可之前。② 该线程被中断之前。③ 设置的时间到之前。并且,当 time为绝对时间时,isAbsolute为 true,否则,isAbsolute为 false。当time为0时,表示无限等待,直到 unpark发生。
  • Unsafe.unpark函数:释放线程的许可,激活调用 park后阻塞的线程。该函数不是安全的,调用该函数时要确保线程依旧存活。

3.1.2waitnotify/notifyAll

在看park()和unpark()之前,不妨来看下在没有LockSupport之前,是怎么实现让线程等待/唤醒的。

在没有LockSupport之前,线程的挂起和唤醒都是通过Object的wait和notify/notifyAll方法实现。

写一段例子代码,线程A执行一段业务逻辑后调用wait阻塞住自己。主线程调用notify方法唤醒线程A,线程A然后打印自己执行的结果。

public static void main(String[] args) throws Exception {
    final Object obj = new Object();
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        try {
            obj.wait();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    });
    A.start();
    //睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    Thread.sleep(1000);
    obj.notify();
}

执行这段代码,不难发现这个错误:

原因很简单,wait和notify/notifyAll方法只能在同步代码块里用(这个有的面试官也会考察)。所以将代码修改为如下就可正常运行了:

public static void main(String[] args) throws Exception {
    final Object obj = new Object();
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        try {
            synchronized (obj) {
                obj.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    });
    A.start();
    // 睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    Thread.sleep(1000);
    synchronized (obj) {
        obj.notify();
    }
}

那如果咱们换成LockSupport呢?简单得很,看代码:

public static void main(String[] args) throws Exception {
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        LockSupport.park();
        System.out.println(sum);
    });
    A.start();
    // 睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    Thread.sleep(1000);
    LockSupport.unpark(A);
}

3.1.3 LockSupport灵活性

通过上面的例子,我们就能明白LockSupport类就是为了提供与wait和notify/notifyAll方法相同的功能,并且使用起来更加简单方便而创造的工具类。

如果只是LockSupport在使用起来比Object的wait/notify简单,那还真没必要专门讲解下LockSupport。最主要的是灵活性。

上边的例子代码中,主线程调用了Thread.sleep(1000)方法来等待线程A计算完成进入wait状态。如果去掉Thread.sleep()调用:

public static void main(String[] args) throws Exception {
    final Object obj = new Object();
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        try {
            synchronized (obj) {
                obj.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    });
    A.start();
    // 睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    //Thread.sleep(1000);
    synchronized (obj) {
        obj.notify();
    }
}

多次执行后,我们会发现:有的时候能够正常打印结果并退出程序,但有的时候线程无法打印结果阻塞住了。原因就在于主线程先调用完notify后,线程A才进入执行wait方法,导致线程A一直阻塞住。由于线程A不是后台线程,所以整个程序无法退出。

那如果换做LockSupport呢?LockSupport就支持主线程先调用unpark后,线程A再调用park而不被阻塞吗?是的,没错。代码如下:

public static void main(String[] args) throws Exception {
    Thread A = new Thread(() -> {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        LockSupport.park();
        System.out.println(sum);
    });
    A.start();
    // 睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
    //Thread.sleep(1000);
    LockSupport.unpark(A);
}

不管你执行多少次,这段代码都能正常打印结果并退出。这就是LockSupport最大的灵活所在。

同样的park()和unpark()也不会遇到Thread.suspend 和 Thread.resume所可能引发的死锁问题。

小结一下,LockSupport比Object的wait/notify有两大优势:

  1. LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。
  2. unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。

3.2 LockSupport中的主要成员及其加载时的初始化

public class LockSupport {
    // Hotspot implementation via intrinsics API
    // UNSAFE字段表示 sun.misc.Unsafe类
    // 一般程序中不允许直接调用
    private static final sun.misc.Unsafe UNSAFE;
    // 而 long型的表示Thread实例对象相应字段在内存中的偏移地址,可以通过该偏移地址获取或者设置该字段的值。
    // 表示Thread类中的parkBlocker对象的内存偏移地址
    private static final long parkBlockerOffset;
    // 表示Thread类中的threadLocalRandomSeed对象的内存偏移地址
    private static final long SEED;
    // 表示Thread类中的threadLocalRandomProbe对象的内存偏移地址
    private static final long PROBE;
    // 表示Thread类中的threadLocalRandomSecondarySeed对象的内存偏移地址
    private static final long SECONDARY;
    // 静态代码块,会在加载时自动执行
    static {
        try {
            // 获取Unsafe实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            // 线程类类型
            Class<?> tk = Thread.class;
            // 获取Thread的parkBlocker字段的内存偏移地址
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            // 获取Thread的threadLocalRandomSeed字段的内存偏移地址
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            // 获取Thread的threadLocalRandomProbe字段的内存偏移地址
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            // 获取Thread的threadLocalRandomSecondarySeed字段的内存偏移地址
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}

不难发现,他们在初始化的时候都是通过Unsafe去获得他们的内存地址。

下面讲一下这几个成员属性。

3.2.1 parkBlockerOffset

表示Thread类中的parkBlocker对象的内存偏移地址,提供给setBlocker和getBlocker使用。

private static void setBlocker(Thread t, Object arg) {
    // Even though volatile, hotspot doesn't need a write barrier here.
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

public static Object getBlocker(Thread t) {
    if (t == null)
        throw new NullPointerException();
    return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}

上面方法中的参数t是Thread线程对象,parkBlocker对象就是Thread类的成员属性

// Thread类的成员属性
volatile Object parkBlocker;

上面的setBlocker和getBlocker方法,就是利用偏移地址parkBlockerOffset操作Thread对象中的parkBlocker。

由于Unsafe.putObject是无视Java访问限制,直接修改目标内存地址的值。即使对象被volatile修饰,也是不需要写屏障的。

这边的偏移量就算Thread这个类里面变量parkBlocker在内存中的偏移量:

JVM的实现可以自由选择如何实现Java对象的“布局“,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。 sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对 Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java 对象的某个字段。

为什么要用偏移量来获取对象?干吗不要直接写个get、set方法?

parkBlocker就是在线程处于阻塞的情况下才被赋值。线程都已经被阻塞了,如果不通过这种内存的方法,而是直接调用线程内的方法,线程是不会回应调用的。

3.2.2 SEED, PROBE, SECONDARY

LockSupport中的这三个成员属性,就是下面这三个Thread类中的成员属性相对应Thread对象的偏移地址。

@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;

都是Thread类中的内存偏移地址,主要用于ThreadLocalRandom类进行随机数生成,它要比Random性能好很多,可以看jdk源码ThreadLocalRandom.java了解详情,这儿就不贴了。

3.3 构造方法

LockSupport 只有一个私有构造函数,无法被实例化。

// 私有构造函数,无法被实例化
private LockSupport() {}

因为LockSupport中定义的都是static静态方法,所以在使用LockSupport时并不需要实例化出一个对象,直接调用类的静态方法即可。

下面我们分析一下LockSupport最常用的几个方法的源码。

3.4 park方法

park 函数有两个重载版本,方法摘要如下:

public static void park();
public static void park(Object blocker);

两个函数的区别在于 park()函数有没有 blocker,即没有设置线程的 parkBlocker字段。

park(Object)型函数如下:

public static void park(Object blocker) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 设置Blocker
    setBlocker(t, blocker);
    // 获取许可
    UNSAFE.park(false, 0L);
    // 重新可运行后再此设置Blocker
    setBlocker(t, null);
}

调用 park函数时,首先获取当前线程,然后设置当前线程的 parkBlocker字段,即调用 setBlocker函数,之后调用 Unsafe类的park函数,之后再调用 setBlocker函数。那么问题来了,为什么要在此 park函数中调用两次 setBlocker函数呢? 

原因其实很简单,调用 park函数时,当前线程首先设置好 parkBlocker字段,然后再调用 Unsafe的 park函数,此后,当前线程就已经阻塞了,等待该线程的 unpark函数被调用,所以后面的一个 setBlocker函数无法运行,unpark函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个 setBlocker,把该线程的 parkBlocker字段设置为null,这样就完成了整个 park函数的逻辑。如果没有第二个 setBlocker,那么之后没有调用 park(Object blocker),而直接调用 getBlocker函数,得到的还是前一个 park(Object blocker)设置的 blocker,显然是不符合逻辑的。总之,必须要保证在 park(Object blocker)整个函数执行完后,该线程的parkBlocker字段又恢复为 null。所以,park(Object)型函数里必须要调用 setBlocker函数两次。

setBlocker方法如下:此方法用于设置线程t 的 parkBlocker字段的值为 arg。

private static void setBlocker(Thread t, Object arg) {
    // 设置线程t的parkBlocker字段的值为arg
    UNSAFE.putObject(t, parkBlockerOffset, arg);
}

另外一个无参重载版本,park()函数如下。

public static void park() {
    // 获取许可,设置时间为无限长,直到可以获取许可
    UNSAFE.park(false, 0L);
}

调用了park函数后,会禁用当前线程,除非许可可用。在以下三种情况之一发生之前,当前线程都将处于休眠状态,即下列情况发生时,当前线程会获取许可,可以继续运行。

  1. 其他某个线程将当前线程作为目标调用 unpark;
  2. 其他某个线程中断当前线程;
  3. 该调用不合逻辑地(即毫无理由地)返回;

3.5 parkNanos 方法

此函数表示在许可可用前禁用当前线程,并最多等待指定的等待时间。具体函数如下。该函数也是调用了两次 setBlocker函数,nanos参数表示相对时间,表示等待多长时间。

public static void park() {
    // 获取许可,设置时间为无限长,直到可以获取许可
    UNSAFE.park(false, 0L);
}

3.6 parkUntil 方法

此函数表示在指定的时限前禁用当前线程,除非许可可用,具体函数如下:该函数也调用了两次 setBlocker函数,deadline参数表示绝对时间,表示指定的时间。

public static void parkUntil(Object blocker, long deadline) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 设置Blocker
    setBlocker(t, blocker);
    UNSAFE.park(true, deadline);
    // 设置Blocker为null
    setBlocker(t, null);
}

3.7 unpark 方法

此函数表示如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。具体函数如下:释放许可,指定线程可以继续运行。

public static void unpark(Thread thread) {
    if (thread != null) // 线程为不空
        UNSAFE.unpark(thread); // 释放该线程许可
}

3.8 LockSupport原理总结

通过学习上面几个方法的源码,我们就发现LockSupport的底层实现都是基于Unsafe.park()和Unsafe.unpark()。

Unsafe源码也相对简单,看下就行了:

void
sun::misc::Unsafe::unpark (::java::lang::Thread *thread)
{
  natThread *nt = (natThread *) thread->data;
  nt->park_helper.unpark ();
}
 
void
sun::misc::Unsafe::park (jboolean isAbsolute, jlong time)
{
  using namespace ::java::lang;
  Thread *thread = Thread::currentThread();
  natThread *nt = (natThread *) thread->data;
  nt->park_helper.park (isAbsolute, time);
}

总之使用park和unpark进行线程的阻塞和唤醒操作,LockSuport.park和LockSuport.unpark是基于Unsafe类中的park()和unpark()方法来实现的,而再往底层看,Unsafe又是借助系统层(C语言)方法pthread_cond_wait和pthread_cond_signal来操作pthread_u和pthread_cond实现的,通过pthread_cond_wait函数可以对一个线程进行阻塞操作,在这之前,必须先获取pthread_mutex,通过pthread_cond_signal函数对一个线程进行唤醒操作。

pthread_mutex和pthread_cond使用示例如下:

void *r1(void *arg)
{
    pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
    static int cnt = 10;
    while(cnt--)
    {
        printf("r1: I am wait.\n");
        pthread_mutex_lock(mutex);
        /* mutex参数用来保护条件变量的互斥锁,调用pthread_cond_wait前mutex必须加锁 */
        pthread_cond_wait(&cond, mutex); 
        pthread_mutex_unlock(mutex);
    }
    return "r1 over";
}

void *r2(void *arg)
{
    pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
    static int cnt = 10;
    while(cnt--)
    {
        pthread_mutex_lock(mutex);
        printf("r2: I am send the cond signal.\n");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(mutex);
        sleep(1);
    }
    return "r2 over";
}

注意,Linux下使用pthread_cond_signal的时候,会产生“惊群”问题的,但是Java中是不会存在这个“惊群”问题的,那么Java是如何处理的呢?

实际上,Java只会对一个线程调用pthread_cond_signal操作,这样肯定只会唤醒一个线程,也就不存在所谓的惊群问题。Java在语言层面实现了自己的线程管理机制(阻塞、唤醒、排队等),每个Thread实例都有一个独立的pthread_u和pthread_cond(系统层面的/C语言层面),在Java语言层面上对单个线程进行独立唤醒操作。(Java中线程只能在Java线程库的指挥下作战,无法直接获取同一个pthread_mutex或者pthread_cond。Java这种实现线程机制的实现实在太巧妙了,虽然底层都是使用pthread_mutex和pthread_cond这些方法,但是貌似C/C++还没这么强大易用的线程库)

具体LockSuuport.park和LockSuuport.unpark的底层实现可以参考对应JDK源码,下面看一下gdb打印处于LockSuuport.park时的线程状态信息:

由上图可知底层确实是基于pthread_cond函数来实现的。

我们在使用LockSupport过程中,多次调用unpark方法和调用一次unpark方法效果一样,因为都是直接将_counter赋值为1,而不是加1。简单说就是:线程A连续调用两次LockSupport.unpark(B)方法唤醒线程B,然后线程B调用两次LockSupport.park()方法, 线程B依旧会被阻塞。因为两次unpark调用效果跟一次调用一样,只能让线程B的第一次调用park方法不被阻塞,第二次调用依旧会阻塞。

四、中断响应

import java.util.concurrent.locks.LockSupport;
class MyThread extends Thread {
    private Object object;
    public MyThread(Object object) {
        this.object = object;
    }
    public void run() {
        System.out.println("before interrupt");
        try {
            // 休眠3s  为了让主线程先执行park()
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread thread = (Thread) object;
        // 3、执行完park之后,执行中断线程
        thread.interrupt();
        System.out.println("after interrupt");
    }
}
public class InterruptDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(Thread.currentThread());
        // 1、执行线程
        myThread.start();
        System.out.println("before park");
        // 2、执行park,获取许可
        LockSupport.park("ParkAndUnparkDemo");
        // 4、线程被成功唤醒了
        System.out.println("after park");
    }
}

运行结果:

before park
before interrupt
after interrupt
after park

可以看到,在主线程调用 park阻塞后,在 myThread线程中发出了中断信号,此时主线程会继续运行,也就是说明此时 interrupt起到的作用与 unpark一样。

总之,线程使用LockSupport.park方法被阻塞后,然后被interrupt()方法唤醒之后,该线程就再也不会被 LockSupport.park方法阻塞了,会被直接唤醒。但是被interrupt()方法唤醒之后,该线程仍然可以再次被LockSupport.park方法阻塞。


参考文章:https://www.cnblogs.com/zhengzhaoxiang/p/13973980.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/345520.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyEclipse技术全面解析——EJB开发工具介绍(一)

MyEclipse v2022.1.0正式版下载1. MyEclipse EJB开发工具Enterprise Java Beans (EJB) 已经成为实现Java企业业务功能和与数据库资源接口的Java EE 5标准&#xff0c;MyEclipse EJB3工具支持Java EE 5简化的基于注释的POJO编程模型&#xff0c;这些工具使开发人员能够在几分钟内…

微信怎么群发消息给所有人?图文教学,快速弄懂

​微信作为很多小伙伴经常使用的工具&#xff0c;无论是学习、工作还是其他方面都会使用到。有些时候&#xff0c;需要将同一条消息发给通讯录里的大多数人&#xff0c;一条一条的转发太慢了&#xff0c;群发消息给所有人是个不错的办法。微信怎么群发消息给所有人&#xff1f;…

广东省基层就业补贴

基层就业补贴链接&#xff1a;https://www.gdzwfw.gov.cn/portal/v2/guide/11440309MB2D27065K4440511108001 一.申请条件&#xff1a; 1、劳动者到中小微企业、个体工商户、社会组织等就业&#xff0c;或到乡镇&#xff08;街道&#xff09;、村居社会管理和公共服务岗位就业…

spring cloud篇——什么是服务熔断?服务降级?服务限流?spring cloud有什么优势?

文章目录一、spring cloud 有什么优势二、服务熔断2.1、雪崩效应2.2、DubboHystrixCommand三、服务降级四、服务限流4.1、限流算法4.2、应用级限流4.3、池化技术4.4、分布式限流4.5、基于Redis 功能的实现限流4.6、基于令牌桶算法的实现4.6.1 、Java实现一、spring cloud 有什么…

GUI swing和awt

GUI&#xff08;Graphical User Interface&#xff0c;简称 GUI&#xff0c;图形用户界面&#xff09;是指采用图形方式显示的计算机操作用户界面&#xff0c;与早期计算机使用的命令行界面相比&#xff0c;图形界面对于用户来说在视觉上更易于接受。Java GUI主要有两个核心库&…

【计算机网络】传输层TCP协议

文章目录认识TCP协议TCP协议的格式字段的含义序号与确认号六个标志位窗口大小确认应答(ACK)机制超时重传机制连接管理机制三次握手四次挥手滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况总结认识TCP协议 传输控制协议 &#xff08;TCP&#xff0c;T…

多边形网格算法笔记

本文是处理多边形和网格的各种笔记和算法。 推荐&#xff1a;使用 NSDT场景设计器 快速搭建 3D场景。 1、表面简化 下面描述了一种方法&#xff0c;用于减少构成表面表示的多边形数量&#xff0c;同时仍试图保留表面的基本形式。 如果正在为渲染和/或交互环境寻求性能改进&…

【CS224图机器学习】task1 图机器学习导论

前言&#xff1a;本期学习是由datawhale&#xff08;公众号&#xff09;组织&#xff0c;由子豪兄讲解的202302期CS224图机器学习的学习笔记。本次学习主要针对图机器学习导论做学习总结。1.什么是图机器学习&#xff1f;通过图这种数据结构&#xff0c;对跨模态数据进行整理。…

增减序列(差分)

分析&#xff1a;要想把整个数组变为同一个数&#xff0c;我们可以根据差分的思想来做。 差分定义&#xff1a;b[1]a[1] b[2]a[2]-a[1] ...... b[i]a[i]-a[i-1] 由定义可知&#xff0c;可以把b[2]~b[n]全部变为0&#xff0c;那么整个数组就一样了。现在问题转换为如何用最少的…

Seata-Server分布式事务原理加源码 (八) - Seata-XA模式

Seata-XA模式 Seata 1.2.0 版本重磅发布新的事务模式&#xff1a;XA 模式&#xff0c;实现对 XA 协议的支持。 我们从三个方面来深入分析&#xff1a; XA模式是什么&#xff1f;为什么支持XA&#xff1f;XA模式如何实现的&#xff0c;以及如何使用&#xff1f; XA模式 首先…

shell学习1

目录 一、echo 1.1 echo 1.2 打印彩色文本 1.3 打印彩色背景 二、printf 三、变量和环境变量 3.1 查看某个进程的环境变量 3.2给变量赋值。varvalue 3.3 给环境变量赋值 3.4 获取变量的长度 3.5 识别当前所使用的shell 3.6 检查是否为超级用户 四、数学运算 4.1 …

PHP新特性集合

php8新特性命名参数function foo(string $a, string $b, ?string $c null, ?string $d null) { /* … */ }你可以通过下面的方式传入参数进行调用foo(b: value b, a: value a, d: value d, );联合类型php7class Number {/** var int|float */private $number;/*** param f…

Vue|事件处理

事件处理1. 事件使用1.1 事件绑定1.2 事件参数2. 事件修饰符2.1 阻止默认事件2.2 阻止事件冒泡2.3 事件只允许触发一次2.4 事件捕获2.5 操作当前元素2.6 行为立即执行无需等待回调3. 键盘事件4. 本章小结4.1 事件使用小结4.2 事件修饰符小结4.3 键盘事件小结1. 事件使用 1.1 事…

C++STL剖析(八)—— unordered_set和unordered_multiset的概念和使用

文章目录前言1. unordered_set的介绍和使用&#x1f351; unordered_set的构造&#x1f351; unordered_set的使用&#x1f345; insert&#x1f345; find&#x1f345; erase&#x1f345; size&#x1f345; empty&#x1f345; clear&#x1f345; swap&#x1f345; count…

安全多方计算系列笔记1——前世今生

这一系列笔记参考了绿盟科技研究通讯的安全多方计算文章&#xff0c;及其他。 首先看定义&#xff1a;在不泄露参与方原始输入数据的前提下&#xff0c;允许分布式参与方合作计算任意函数&#xff0c;输出准确的计算结果。 起源 安全多方计算问题及解首先由姚期智&#xff08…

用大白话给你科普,到底什么是 API(应用程序编程接口)?

何为API&#xff1f;如果你在百度百科上搜索&#xff0c;你会得到如下结果&#xff1a;API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件得以访问一组…

Linux 安装Python3

python3 下载地址 python3 下载地址 https://www.python.org/downloads/ 选择自己需要的版本、 此文中选择3.10.9 下载源码压缩包 可下载到本地后上传至Linux服务器也可以复制下载地址 wget https://www.python.org/ftp/python/3.10.9/Python-3.10.9.tgzpython3 安装 yum…

python练习——简化路径

项目场景&#xff1a; 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路径 &#xff08;以 /开头&#xff09;&#xff0c;请你将其转化为更加简洁的规范路径。在 Unix 风格的文件系统中&#xff0c;一个点&#xff08;.&#xff09;表示当前目录本…

新库上线 | CnOpenData中国地方政府债券信息数据

中国地方政府债券信息数据 一、数据简介 地方政府债券 指某一国家中有财政收入的地方政府地方公共机构发行的债券。地方政府债券一般用于交通、通讯、住宅、教育、医院和污水处理系统等地方性公共设施的建设。地方政府债券一般也是以当地政府的税收能力作为还本付息的担保。地…

【电源专题】案例:用电子负载模拟电池测试充电芯片

最近在做有关充电芯片的选型,所以需要测试充电芯片中的很多参数。如涓流充电电流、快速充电电流、截止电流等等。 如下所示为某充电IC充电过程中的电流电压变化曲线。可以看出其中存在多个电流如Ishort/Iterm/Iprechg/Ichgerg等等。电压点也有Vshortz/Vbatlow/Vbatreg等。这些…