【JUC基础】09. LockSupport

简介: LockSupport是一个线程阻塞工具,可以在线程任意位置让线程阻塞。线程操作阻塞的方式其实还有Thread.suspend()和Object.wait()。而LockSupport与suspend()相比,弥补了由于resume()方法而导致线程被挂起(类似死锁)的问题,也弥补了wait()需要先获得某个对象锁的问题,也不会抛出InterruptedException异常。

1、什么是LockSupport

LockSupport是一个线程阻塞工具,可以在线程任意位置让线程阻塞。线程操作阻塞的方式其实还有Thread.suspend()和Object.wait()。而LockSupport与suspend()相比,弥补了由于resume()方法而导致线程被挂起(类似死锁)的问题,也弥补了wait()需要先获得某个对象锁的问题,也不会抛出InterruptedException异常。

image.png

相关API:

image.png

2、suspend()和resume()

说到线程阻塞,除了Object.wait()和Object.notify()两种以外,还有Thread.suspend()和Thread.resume()。不过当前这两组API已被弃用,因为他们可能会导致死锁情况发生。

image.png

image.png

示例代码:

package locksupport;
import java.util.concurrent.TimeUnit;
/**
 * @author Shamee loop
 * @date 2023/5/19
 */
public class ThreadSuspendTest {
    static Object syncObject = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (syncObject) {
                System.out.println(Thread.currentThread().getName() + "执行中,准备挂起");
                // 线程挂起
                Thread.currentThread().suspend();
            }
        }, "Thread-1");
        Thread thread2 = new Thread(() -> {
            synchronized (syncObject) {
                System.out.println(Thread.currentThread().getName() + "执行中,准备挂起");
                // 线程挂起
                Thread.currentThread().suspend();
            }
        }, "Thread-2");
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        thread2.start();
        // 继续执行
//        System.out.println("thread1线程准备执行了resume");
        thread1.resume();
//        System.out.println("thread1线程执行了resume");
        // 继续执行
//        System.out.println("thread2线程准备执行了resume");
        thread2.resume();
//        System.out.println("thread2线程执行了resume");
        thread1.join();
        thread2.join();
        System.out.println("程序运行结束");
    }
}

image.gif

这里模拟了两个线程,运行过程中suspend()挂起,然后使用resume()让线程继续执行。

运行结果:

image.png

可以看到程序无法退出,也无法打印“程序运行结束”日志。这就说明程序被永远挂起。原因是suspend()在导致线程暂停的同时,不会释放任何资源。此时其他线程想要访问被占用的锁时,都会导致阻塞。直到线程上进行了resume(),被挂起的线程才能继续。但是如果resume()方法操作以外的在suspend()之前进行了,那么被挂起的资源就尽可能永远被挂起而无法继续。

这里更加注意的时候,这时候被挂起的线程,状态还是Runnable,这些估计也是JDK不推荐的原因吧。

我们将上面几行注释的代码打开,在运行以下:

image.png

我们发现thread2的resume()方法在suspend()之前就进行了。因此也解释了为什么thread2没有被继续执行的原因。

3、park()和unpark()

官方文档说了,LockSupport有两个重要的方法,park()和unpark()。而这两个方法很好的弥补了suspend()和resume()的不足。我们上述同等代码,用LockSupport来试下。

package locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
 * @author Shamee loop
 * @date 2023/5/19
 */
public class LockSupportTest {
    static Object syncObject = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (syncObject) {
                System.out.println(Thread.currentThread().getName() + "执行中,准备挂起");
                LockSupport.park();
            }
        }, "Thread-1");
        Thread thread2 = new Thread(() -> {
            synchronized (syncObject) {
                System.out.println(Thread.currentThread().getName() + "执行中,准备挂起");
                LockSupport.park();
            }
        }, "Thread-2");
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        thread2.start();
        // 继续执行
//        System.out.println("thread1线程准备执行了unpark");
        LockSupport.unpark(thread1);
//        System.out.println("thread1线程执行了unpark");
        // 继续执行
//        System.out.println("thread2线程准备执行了unpark");
        LockSupport.unpark(thread2);
//        System.out.println("thread2线程执行了unpark");
        thread1.join();
        thread2.join();
        System.out.println("程序运行结束");
    }
}

image.gif

运行结果:

image.png

可以看到程序正常退出,也打印出“程序运行结束”日志。而代码中只是将原本的suspend()方法替换成LockSupport.park(),将thread1.resume()替换成LockSupport.unpark(thread1)。

这是因为LockSupport使用的是类似信号量的机制。他为每个线程准备了一个许可,如果许可可用,那么park()方法会立刻返回,并且消费这个许可(也就是将许可变成不可用)。如果许可不可用,就会阻塞,而unpark()则是使得一个许可变成可用,但是和信号量不同的时,许可不能累加,不可能拥有超过一个许可。

所以这个特点就使得,即使unpark()方法执行发生在park()之前,他也可以是下一次的park()方法立即返回(因为当前的许可是可用的)。

不仅如此,通常使用park()挂起的状态,不会像suspend()还是Runnabele,他会直接给出waiting状态,同时堆栈也会体现由于park()方法引起,因此相比suspend()来说也更加友好。

我们将上述代码的注释打开,看下运行结果:

image.png

可以看到,thread2的unpark()就算运行在了park()之前,程序依然正常运行结束。

4、中断影响

LockSupport方法还支持中断影响。但是他的中断并不是直接抛出InterruptedException,而是默默返回,我们可以从Thread.interrupted方法中获得中断标记。

如代码:

package locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
 * @author Shamee loop
 * @date 2023/5/19
 */
public class LockSupportTest {
    static Object syncObject = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(LockSupportTest::doExecuteOn, "Thread-1");
        Thread thread2 = new Thread(LockSupportTest::doExecuteOn, "Thread-2");
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        thread2.start();
        thread1.interrupt();
        LockSupport.unpark(thread2);
    }
    private static void doExecuteOn(){
        synchronized (syncObject) {
            System.out.println(Thread.currentThread().getName() + "执行中,准备挂起");
            LockSupport.park();
            if(Thread.interrupted()){
                System.out.println(Thread.currentThread().getName() + "中断了");
            }
            System.out.println(Thread.currentThread().getName() + "执行结束");
        }
    }
}

image.gif

执行结果:

image.png

我们可以看到thread1可以立马响应中断,并且返回,甚至都不用unpark()。返回后,外面的thread2才能占用资源,并最终由unpark(thread2)操作,程序结束。

5、小结

前面在《【JUC基础】08. 三大工具类》中我们介绍了三大使用的线程辅助类,这里单独介绍线程阻塞控制类。因为我觉得这个工具类和前面的辅助类还是不太一样的。线程的阻塞,唤起是多线程开发中不可或缺的,因此单独拿出来讲。这里使用简单的代码程序说明,希望能有所收获。一天进步一点点~

相关文章
|
7月前
|
Java
JUC并发编程之等待唤醒机制
在JUC(Java Util Concurrent)并发编程中,线程等待唤醒机制是实现线程之间协作和同步的重要手段。这种机制允许一个线程挂起等待某个条件满足后被唤醒,以及另一个线程在满足某个条件后唤醒等待的线程。在Java中,有多种实现线程等待唤醒机制的方式,包括使用Object的wait()和notify()方法、Condition接口以及LockSupport类。
|
4月前
|
安全 Java
JUC(3)
这篇文章讨论了Java集合类在高并发情况下的不安全性,并介绍了使用CopyOnWriteArrayList、Vector、ConcurrentHashMap等线程安全集合来解决这些问题的方法。
JUC(3)
|
7月前
|
Java
Java一分钟之-并发编程:线程间通信(Phaser, CyclicBarrier, Semaphore)
【5月更文挑战第19天】Java并发编程中,Phaser、CyclicBarrier和Semaphore是三种强大的同步工具。Phaser用于阶段性任务协调,支持动态注册;CyclicBarrier允许线程同步执行,适合循环任务;Semaphore控制资源访问线程数,常用于限流和资源池管理。了解其使用场景、常见问题及避免策略,结合代码示例,能有效提升并发程序效率。注意异常处理和资源管理,以防止并发问题。
70 2
|
7月前
|
安全 Java 程序员
Java多线程基础-17:简单介绍一下JUC中的 ReentrantLock
ReentrantLock是Java并发包中的可重入互斥锁,与`synchronized`类似但更灵活。
62 0
|
安全 Java
JUC第十一讲:JUC锁LockSupport详解
JUC第十一讲:JUC锁LockSupport详解
139 0
|
Java 编译器 调度
JUC是什么?
JUC是什么?
|
消息中间件 资源调度 Java
【JUC基础】01. 初步认识JUC
前段时间,有朋友跟我说,能否写一些关于JUC的教程文章。本来呢,JUC也有在我的专栏计划之内,只是一直都还没空轮到他,那么既然有这样的一个契机,那就把JUC计划提前吧。那么今天就重点来初步认识一下什么是JUC,以及一些基本的JUC相关基础知识。
180 0
【JUC基础】01. 初步认识JUC
|
Java
JUC
JUC
110 0
|
Java 编译器 调度
什么是JUC
并发编程学习
568 0
什么是JUC
|
Java
Java并发编程 - AQS 简介(AbstractQueuedSynchronizer)
Java并发编程 - AQS 简介(AbstractQueuedSynchronizer)
154 0
Java并发编程 - AQS 简介(AbstractQueuedSynchronizer)