LockSupport的使用

LockSupport的使用

LockSupport

目录LockSupport一、为什么需要LockSupport类二、LockSupport灵活性小结一下,LockSupport比Object的wait/notify有两大优势:三、原理3.1、park方法3.2、unpark方法3.3、上面案例回顾分析四、LockSupport使用4.1、park住的线程没有被中断,只会阻塞4.2、park住的线程被正常唤醒4.3、park线程被中断唤醒4.4、park住的线程和Thread.interupted()结合五、总结

一、为什么需要LockSupport类

来看下在没有LockSupport之前,是怎么实现让线程等待/唤醒的。在没有LockSupport之前,线程的挂起和唤醒咱们都是通过Object的wait和notify/notifyAll方法实现。

那如果咱们换成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);

}

二、LockSupport灵活性

首先LockSupport在使用起来比Object的wait/notify简单,而且最重要的是灵活性。

上边的例子代码中,主线程调用了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最大的灵活所在。

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

LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。

unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序。

notify只能随机选择一个线程唤醒,无法唤醒指定的线程,unpark却可以唤醒一个指定的线程。

三、原理

那么原理是什么呢?

3.1、park方法

blocker是用来记录线程被阻塞时被谁阻塞的。用于线程监控和分析工具来定位原因的。setBlocker(t, blocker)方法的作用是记录t线程是被broker阻塞的。因此我们只关注最核心的方法,也就是UNSAFE.park(false, 0L)。

UNSAFE是一个非常强大的类,他的的操作是基于底层的,也就是可以直接操作内存,因此我们从JVM的角度来分析一下:

每个java线程都有一个Parker实例:

我们换一种角度来理解一下park和unpark,可以想一下,unpark其实就相当于一个许可,告诉特定线程你可以停车,特定线程想要park停车的时候一看到有许可,就可以立马停车。

现在有了这个概念,我们体会一下上面JVM层面park的方法,这里面counter字段,就是用来记录所谓的“许可”的。

当调用park时,先尝试直接能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回。

如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,如果是,则把_counter设置为0,unlock mutex并返回:

否则,再判断等待的时间,然后再调用pthread_cond_wait函数等待,如果等待返回,则把_counter设置为0,unlock mutex并返回:

总结来说,画个图来表示一下:

3.2、unpark方法

还是先来看一下JDK源码:

上面注释的意思是给线程生产许可证。

当unpark时,则简单多了,直接设置_counter为1,再unlock mutext返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程:

ok,现在我们已经对源码进行了分析,整个过程其实就是生产许可和消费许可的过程。而且这个生产过程可以反过来。也就是先生产再消费。

3.3、上面案例回顾分析

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);

}

这里是不能够确定哪个线程是先执行,哪个后执行的。

1、如果park先执行,发现counter=0,那么等待;在进行unpark的时候,发现=0,先去阻塞的线程执行;

2、如果unpark先执行,那么直接赋值counter=1,而park在执行的时候,发现counter=1之后,就立马执行。

四、LockSupport使用

4.1、park住的线程没有被中断,只会阻塞

public class ThreadWattingTestOne {

public static void main(String[] args) {

final Thread thread1 = new Thread(() -> {

LockSupport.unpark(Thread.currentThread());

System.out.println("hello,world");

LockSupport.park();

System.out.println("hello,park1");

LockSupport.park();

System.out.println("hello,park2");

});

thread1.start();

try {

thread1.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

查看控制台输出:

hello,world

hello,park1

先执行unpark,直接赋值counter=1,然后第一次park执行时,发现counter=1之后,执行之后并将counter设置为0;但是第二次的时候发现counter=0之后,阻塞。所以输出不出来park2

4.2、park住的线程被正常唤醒

public class LockSupportTestTwo {

public static void main(String[] args) {

final Thread thread1 = new Thread(() -> {

for (;;){

System.out.println("hello,world");

LockSupport.park();

System.out.println("hello,park");

}

});

thread1.start();

LockSupport.unpark(thread1);

}

}

控制台打印:

hello,world

hello,park

hello,world

1、如果park先执行,那么第一次就会被阻塞住;而在unpark执行之后,会唤醒阻塞住的线程;第二次循环执行的时候,发现还是为0,那么阻塞;

2、如果unpark先执行,那么counter=1,park消费完成之后,counter=0,然后又会阻塞。

二者执行效果上是一样的。

4.3、park线程被中断唤醒

public class LockSupportTestFour {

public static void main(String[] args) throws InterruptedException {

Thread thread1 = new Thread(() -> {

System.out.println(String.format("当前线程的名称是:%s", Thread.currentThread().getName()));

LockSupport.park();

System.out.println("hello,locksupport");

LockSupport.park();

System.out.println("hello,locksupport");

});

thread1.start();

Thread.sleep(3000);

thread1.interrupt();

}

}

控制台输出:

当前线程的名称是:Thread-0

hello,locksupport

hello,locksupport

也就是说interrupt会中断掉park方法,让其停止阻塞。

再来一下循环的例子会更加直观明显:

public class LockSupportTestThree {

public static void main(String[] args) {

final Thread thread1 = new Thread(() -> {

for (;;){

System.out.println("hello,world");

LockSupport.park();

System.out.println("hello,park");

}

});

thread1.start();

thread1.interrupt();

}

}

控制台循环执行。线程中断打断了让其进入到休眠,所以线程一直处于活跃状态。也就是说线程中断让其许可一直处于可用状态。

因为从Thread中的State状态中的描述可看到,LockSupport.park()会让线程进入到WAITING状态,而线程中断会打破线程休眠。

4.4、park住的线程和Thread.interupted()结合

/**

* @Description 测试LockSupport.park();,只会park住中断标记为false的,不会打断中断标记为true的

* @Author liguang

* @Date 2022/06/20/17:51

*/

public class ParkInteruptTestOne {

private static Logger logger = LoggerFactory.getLogger(ParkInteruptTestOne.class);

public static void main(String[] args) throws InterruptedException {

testParkTwo();

}

/**

* park的线程因为中断而唤醒之后,将无法再park住

* @throws InterruptedException

*/

private static void testParkTwo() throws InterruptedException {

Thread threadA = new Thread(() -> {

// park住的线程是因为中断唤醒的,那么将无法再次park住

logger.info("当前线程正在执行........");

logger.info("当前线程是因为中断而被唤醒的");

LockSupport.park();

// 但是重置标记

Thread.interrupted();

logger.info("当前线程中断标记是:{}",Thread.currentThread().isInterrupted());

logger.info("因为当前线程标记为false,所以会再次阻塞住,不会再次来打印下面的代码");

LockSupport.park();

logger.info("是否会再次来打印下面的代码?");

}, "threadA");

threadA.start();

TimeUnit.SECONDS.sleep(2);

// 发送中断信号之后,再也无法进行park住了

threadA.interrupt();

}

}

五、总结

调用LockSupport.park()/LockSupport.park(this)代码只是会在线程标记状态为TRUE的时候,不会阻塞住当前线程;

侧重点:在于不满足某个条件的时候让当前线程阻塞住,当满足了条件之后,再让当前线程运行起来。

相关养生推荐

躇是什么意思躇的解释 躇怎么读
江苏卫视365直播

躇是什么意思躇的解释 躇怎么读

📅 07-09 👁️ 1525
Win10系统自带的备份,恢复功能
江苏卫视365直播

Win10系统自带的备份,恢复功能

📅 07-03 👁️ 9518
世界杯预选赛摩洛哥 世界杯预选赛摩洛哥小组赛