【并发编程】LockSupport源码详解
目录
一、前言
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 为什么说LockSupport是Java并发的基石?
当需要阻塞或唤醒一个线程的时候,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的功能,不过还是有一些差别的。
- park不需要获取某个对象的锁。
- 因为中断的时候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值)
- unpark调用时,如果当前线程还未进入park,则许可为true,并且不会去执行unpark。
- 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.2wait和notify/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有两大优势:
- LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。
- 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();// 设置BlockersetBlocker(t, blocker);// 获取许可UNSAFE.park(false, 0L);// 重新可运行后再此设置BlockersetBlocker(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字段的值为argUNSAFE.putObject(t, parkBlockerOffset, arg);
}
另外一个无参重载版本,park()函数如下。
public static void park() {// 获取许可,设置时间为无限长,直到可以获取许可UNSAFE.park(false, 0L);
}
调用了park函数后,会禁用当前线程,除非许可可用。在以下三种情况之一发生之前,当前线程都将处于休眠状态,即下列情况发生时,当前线程会获取许可,可以继续运行。
- 其他某个线程将当前线程作为目标调用 unpark;
- 其他某个线程中断当前线程;
- 该调用不合逻辑地(即毫无理由地)返回;
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();// 设置BlockersetBlocker(t, blocker);UNSAFE.park(true, deadline);// 设置Blocker为nullsetBlocker(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
相关文章:
【并发编程】LockSupport源码详解
目录 一、前言 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/notify…...
元宇宙之声:新鸿基公司
在本期节目中,新鸿基团队讲述了他们在农历新年季中展示的元宇宙最新创作! 为什么将体验命名为「乘风启航」?什么是 「Scallywag」? 香港专业离岸帆船队新鸿基 Scallywag 队由新鸿基公司赞助,其团队精神与公司的精神相呼…...
Linux中定时监控Tomcat服务器进程并在进程结束时重启Tomcat服务器
目录一、问题二、解决方法1、创建定时任务文件2、修改Tomcat的部分文件3、添加系统的定时调度4、执行monitor.sh文件5、查看脚本执行的日志文件一、问题 当我们的Tomcat配置完成后投入使用后,在用户使用一定时间后,Tomcat可能会出现一些问题导致进程结束…...
快速部署私有云笔记,免费享受多端同步
一、老Q笔记之一路坎坷 市面上的笔记软件非常多,有些是本地编辑功能特别强大但是不支持云同步,有些是支持上云但是编辑功能不够完善。选择一款合适的云笔记软件,无疑能让我们工、学习的时候更加顺心、顺手。 这么多年来老Q使用过很多云笔记…...
python生成 2048位随机质数 Miller-Rabin质数测试算法
Miller-Rabin质数测试算法是一种基于随机化的算法,用于判断一个数是否为质数。该算法具有高效性和强健性,通常被用于加密算法中生成大素数。 该算法基于以下两个事实:对于质数ppp和任意整数aaa,有ap−1≡1(modp)a^{p-1} \equiv 1…...
♡ — MySQL 查询缓存
MySQL 查询缓存 执行查询语句的时候,会先查询缓存。MySQL 8.0 版本后移除,因为这个功能不太实用。 my.cnf 加入以下配置,重启 MySQL 开启查询缓存: query_cache_type1 query_cache_size600000MySQL 执行以下命令也可以开启查询缓…...
死锁检测组件 -- 使用hook检测死锁
目录 hook hook是什么 dlsym()函数 hook的实现步骤 加入hook的demo C/CLinux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 hook hook可以把系统或第三方库提供的函数,替换成我们写的同名函数。会调用我们实现的函数。 hook是什么 hook提供了两…...
第2集丨Java中的数据类型汇总
目录一、数据类型分类二、基本数据类型取值范围数据类型的转换byte和char的关系三、包装类一、数据类型分类 二、基本数据类型 取值范围 比特(bit位) : 数据运算得最小存储单位字节(byte) : 数据最小存储单位bit和byte可以互相转换得,1 byte 8 bit位默认情况下&am…...
【基础篇】7 # 队列:队列在线程池等有限资源池中的应用
说明 【数据结构与算法之美】专栏学习笔记 什么是队列? 队列是一种操作受限的线性表数据结构,特点是先进先出,最基本的操作有:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头…...
matlab进行双目标定获取双目参数并打印教程
文章目录前言1.打开matlab进行双目标定2.获取想要的参数前言 在相同的标定算法和标定参数下,Python和Matlab的标定精度是相同的。因为标定精度主要取决于标定算法和标定参数的质量,而不是编程语言的选择。 不同的编程语言可能使用不同的库或实现细节&…...
JVM类加载机制
回到2018年的抖音哈哈. 回顾下: java开发环境: java编译运行过程: 1) 编译期:.java源文件,经过编译,生成.class字节码文件 2) 运行期:JVM加载.class并运行.class(0和1) 特点: 跨平台、一次编程,处处报错 名词解释: 1…...
8.1 优化概述
数据库性能取决于数据库级别的几个因素,例如表、查询和配置设置。这些软件结构导致了硬件级别的 CPU 和 I/O 操作,您必须将其最小化并尽可能提高效率。在研究数据库性能时,首先要学习软件端的高级规则和准则,然后使用墙上的时钟时…...
从0到1一步一步玩转openEuler--14 openEuler DNF(YUM)配置管理
文章目录14.1 DNF配置文件14.1.1 配置main部分14.1.2 配置repository部分14.1.3 显示当前配置14.2 创建本地软件源仓库14.3 添加、启用和禁用软件源14.3.1 添加软件源14.3.2 禁用软件源14.3.3 启用软件源DNF是一款Linux软件包管理工具,用于管理RPM软件包。DNF可以查…...
leetcode707 设计链表 带有输入和输出的
题目: 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节…...
100种思维模型之非sr思维模型-012
什么是sr? sr是stimulus-response的缩写,意思是刺激反应。 那么非sr思维模型就是非刺激反应思维模型的意思。 今天我们来聊聊非sr思维模型——一个提醒我们思考,提醒我们任何时刻都有选择权的思维模型。 本文依然从三个方面进行介绍,何谓…...
绿竹生物再冲刺港交所上市:暂未商业化,孔健夫妇为实控人
近日,北京绿竹生物技术股份有限公司(下称“绿竹生物”)在港交所递交招股书,准备在港交所主板上市,中金公司为其独家保荐人。据贝多财经了解,绿竹生物曾于2022年6月28日在港交所递表。 相较于此前招股书&am…...
加拿大MSB金融牌照申请方案
什么是加拿大MSB金融牌照? 根据犯罪所得(洗钱)和恐怖主义融资法案,您的企业必须在加拿大金融交易和报告分析中心 (FINTRAC) 注册成为货币服务企业。自 2020 年 6 月 1 日起,外国货币服务企业也必须在 FINTRAC 注册&…...
javaEE 初阶 — 滑动窗口
文章目录滑动窗口1 滑动窗口下如何处理丢包TCP 工作机制:确认应答机制 超时重传机制 连接管理机制 滑动窗口 确认应答机制、超时重传机制、连接管理机制 都是给 TCP 的可靠性提供支持的。 虽然事变的比较可靠了,但是是有牺牲的,那就是传输…...
大咖说·图书分享|狼书(卷3):Node.js高级技术
Node.js都有哪些需要掌握的高级技术?前端为什么同样需要学习? Node.js未来的发展趋势究竟如何?本期大咖说,Node布道师桑世龙携新作《狼书(卷3):Node.js高级技术》展开分享。 ● 嘉宾介绍 桑世龙:Node布道…...
1.5配置NBMA和P2MP网络类型
1.3.3实验5:配置NBMA和P2MP网络类型 1. 实验需求 控制OSPF DR的选举修改OSPF的网络类型2. 实验拓扑 配置NBMA和P2MP网络类型实验拓扑如图1-13所示。 图1-13 配置NBMA和P2MP网络类型 3. 实验步骤 帧中继的配置如图1-14和图1-15所示...
Java面试题
三次握手,四次挥手中,为什么要挥手四次 第一次握手,客户端发送同步报文到服务端,客户端知道自己有发送数据能力,不知道服务端是否有发送、接受数据能力。 第二次握手,服务端收到同步报文,并回复…...
opencv锁定鼠标定位
大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...
机器连接和边缘计算
以一种高效、可扩展的方式进行连接和边缘计算的结合,解决了在工业物联网应用中的机器数据集成问题。 一 边缘计算 边缘计算描述了由中央平台管理的数据分散式处理。边缘计算对于工业物联网而言非常重要。在许多应用程序中,由于数据量非常大,…...
利用NGROK将本地网站发布为一个公开网站
一般与第三方服务集成时,需要提供https的回调URL,本地开发阶段可以利用NGROK将本地网站发布为公开的https网站。https://ngrok.com/downloadWindow下载地址:https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-windows-amd64.zip以Window…...
Vulnhub 渗透练习(一)—— Breach 1.0
环境搭建 环境下载: https://www.vulnhub.com/entry/breach-1,152/ 环境描述: Vulnhub 中对此环境的描述: VM 配置有静态 IP 地址 (192.168.110.140),因此您需要将仅主机适配器配置到该子网。 这里我用的是 VMware ࿰…...
初探Spring采用Spring配置文件管理Bean
文章目录Spring容器演示--采用Spring配置文件管理Bean(一)创建Maven项目(二)添加Spring依赖(三)创建杀龙任务类(四)创建勇敢骑士类(五)采用传统方式让勇敢骑士…...
【手写 Vuex 源码】第十二篇 - Vuex 插件机制的实现
一,前言 上一篇,主要介绍了 Vuex 插件的开发,主要涉及以下几个点: Vuex 插件的使用介绍;Vuex 插件开发和使用分析;Vuex 插件机制的分析; 本篇,继续介绍 Vuex 插件机制的实现&…...
图像去噪技术简述
随着每天拍摄的数字图像数量激增,对更准确、更美观的图像的需求也在增加。然而,现代相机拍摄的图像不可避免地会受到噪声的影响,从而导致视觉图像质量下降。因此,需要在不丢失图像特征(边缘、角和其他尖锐结构…...
数据迁移——技术选型
日常我们在开发中,随着业务需求的变更,重构系统是很常见的事情。重构系统常见的一个场景是变更底层数据模型与存储结构。这种情况下就要对数据进行迁移,从而使业务能正常支行。 背景如下:老系统中使用了mongo数据库,由…...
第二十七章 java并发常见知识内容(CompletableFuture)
JAVA重要知识点CompletableFuture常见函数式编程操作创建 CompletableFuture静态工厂方法处理异步结算的结果异常处理组合 CompletableFuturethenCompose() 和 thenCombine() 区别并行运行多个 CompletableFutureCompletableFuture Java 8 才被引入的一个非常有用的用于异步编…...
芜湖网站制作/百度关键词搜索量排行
AngularJS是一个javascript框架,通过AngularJS这个类库可以实现目前比较流行的单页面应用,AngularJS还具有双向数据绑定的特点,更加适应页面动态内容。所谓单页面应用就是在同一个页面动态加载不同的内容,而这里的“跳转”可以理解…...
wordpress怎么查看数据库/ttkefu在线客服系统官网
参考http://ourjs.com/detail/52ad26ff127c76320300001f Offcial Site http://requirejs.org/ 下载http://requirejs.org/docs/download.html 需要require.js才能正确执行require 和 define 为什么需要模块化JS 下面是ourJS的说法 这样的写法有很大的缺点。首先,加…...
企业数字化建设公司/seo推广排名软件
说明 目前互联网公司,大部分项目都是基于分布式,一个项目被拆分成几个小项目,这些小项目会分别部署在不同的计算机上面,这个叫做微服务。当一台计算机的程序需要调用另一台计算机代码的时候,就涉及远程调用。此时dubbo…...
比较好看的网站设计/自己的网站怎么推广
在创建属于自己的浏览器前,先介绍一个WebBrowser控件,它为WebBrowser ActiceX控件提供了托管包装,托管包装使用户可以在Windows窗体客户端应用程序中显示网页,使用WebBrowser控件,可以复制用用程序中的Internet Explor…...
北镇网站建设/网络营销理论基础
1. 数学概念的提出 首先使用“函数”一词的是莱布尼兹(Leibnitz,1646年7月1日-1716年11月14日)自 17 世纪起近代数学产生以来,函数的概念一直处于数学思想的真正核心位置。2. 数学流派 彼得堡学派: 彼得堡&…...
wordpress编辑器不习惯/5年网站seo优化公司
要求:每月1日0点:在不影响业务的情况下,备份整月的数据,保留6次备份。思路:基于MYSQL事件功能,每月按时完成操作RENAME语句具有原子性,新旧表无缝切换RENAME语句仅修改表定义,大表瞬间完成基于上面三点,实现了表数据按…...