Java并发编程(二):LockSupport应用及原理分析

1 引言

上一篇简单介绍了Java中的Thread机制,附传送门↓:
Java并发编程(一):了解Thread

我们知道,可以通过wait()让线程等待,通过notify()唤醒线程,但使用wait(),notify()来实现等待唤醒功能至少有两个缺点:

  1. wait()notify()都是Object中的方法,在调用这两个方法前必须先获得锁对象,这限制了其使用场合:只能在同步代码块中。

  2. 另一个缺点是当对象的等待队列中有多个线程时,notify()只能随机选择一个线程唤醒,无法唤醒指定的线程

而使用LockSupport的话,我们可以在任何场合使线程阻塞,同时也可以指定要唤醒的线程,较为方便。

2 LockSupport的应用

首先列举LockSupport常用的几个方法:

// 暂停当前线程
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);

方法入参的Object blocker是代表什么呢?这其实就是方便在线程dump的时候看到具体的阻塞对象的信息

再通过一个小栗子简单演示LockSupport的用法:

/**
 * @author Carson Chu, 1965704869@qq.com
 * @date 2020/4/4 10:41
 * @description
 */
public class Main {
    public static void main(String[] args) {
        FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
            for (int i = 0; i < 2; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            return true;
        });
        Thread thread1 = new Thread(futureTask, "thread-1");
        Thread thread2 = new Thread(() -> {
            for (int i = 2; i < 4; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
                /* 让线程等待 */
                LockSupport.park();
            }
        }, "thread-2");
        thread1.start();
        thread2.start();
        LockSupport.unpart(thread2);
    }
}

由上述代码可以看出,线程2在调度的时候,进入循环第一次输出数字时,调用的LockSupport.park()方法,所以线程2只会输出一个数字然后进入等待状态。当然最后不要忘记调用LockSupport.unpark()唤醒。

在了解LockSupport的等待唤醒的基本操作之后,再来看下面一段代码:

public class Main {
    public static void main(String[] args) {
        FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
            Thread currentThread = Thread.currentThread();
            for (int i = 0; i < 2; i++) {
                System.out.println(currentThread.getName() + ":" + i);
                /* 唤醒线程 */
                LockSupport.unpark(currentThread);
            }
            /* 让线程等待 */
            LockSupport.park();
            return true;
        });
        Thread thread1 = new Thread(futureTask, "thread-1");
        thread1.start();
    }
}

上述代码关键在于唤醒操作执行在了等待操作之前,那么线程是否会发生死锁呢?

答案是不会,这点不同于stopresume机制,stopresume如果顺序反了,会出现死锁现象。

那么是什么原因呢?

让我们来结合源码分析。
首先贴出park()的实现原理:

public static void park() {
        /* 该方法是本地方法native */
        UNSAFE.park(false, 0L);
    }

再贴出unpark()的实现原理:

public static void unpark(Thread thread) {
        if (thread != null)
            /* 该方法是本地方法native */
            UNSAFE.unpark(thread);
    }

观察上述源码,尤其是park(),眼光锐利如你,相信一定看到了false这个关键字,没错,这就是重点。实际上LockSupport底层维护了一个许可(permit),即上述源码里的布尔值。那么它是什么原理呢?一言以蔽之就是:

park()调用时,判断许可是否为true,如果是true,则继续往下执行;如果是false,则调用线程等待,直到许可为true。当unpark()调用时,如果当前线程还未进入park(),则许可为true。

再结合上述样例代码,我先调用unpark()方法,此时线程还有进入park(),所以许可为true,之后再调用park()方法时,判断许可值为true,则线程不会执行等待操作,而是继续执行,因而不会发生死锁。

小结

  1. park()unpark()可以实现类似wait()notify()的功能,但是并不和wait()notify()交叉,也就是说unpark()不会对wait()起作用,notify()也不会对park()起作用。
  2. park()unpark()的使用不会出现死锁的情况。

点点关注,不会迷路

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值