LockSupport 类的 park() 和 unpark() 作用和案例分析

官方解释:

  • LockSupport类是用来创建锁和其他同步类的基本线程阻塞原语;

  • LockSupport 类使用了一种名为 Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit);

  • permit 只有两个值1和零,默认是零;

  • 可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1

其实它就是线程的等待唤醒机制的改良加强版;

LockSupport类中的park()unpark()的作用分别是阻塞线程和解除阻塞线程;

我们知道Object类中有wait()notify()等待唤醒线程的方法,juc 包中Conditon接口类也有await()signal()等待唤醒线程的方法, 为什么还要来个LockSupport类的park()和unpark()的等待唤醒线程的方法呢?

我们来看下Object类和 juc 包中Conditon接口类使用线程等待唤醒方法分别有什么限制;


1. 测试案例

1.1 wait 和 notify方法

public class LockSupportDemo1 {

    // 加锁对象
    static Object objectLock = new Object();

    public static void main(String[] args) {

        // 第一个线程调用 wait()方法
        new Thread(()->{
            /*try {
                TimeUnit.SECONDS.sleep(1L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName() + ":进入并等待");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":被唤醒");
            }
        },"A").start();

        // 第二个线程调用 notify()方法,唤醒线程A
        new Thread(()->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + ":通知");
            }
        },"B").start();
    }
}

正常返回:

A:进入并等待
B:通知
A:被唤醒
  • 异常一:如果把两个线程的同步代码块方法去掉【10,18,23,26行】,抛出异常:
A:进入
Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.beef.LockSupportDemo.lambda$main$1(LockSupportDemo.java:30)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.beef.LockSupportDemo.lambda$main$0(LockSupportDemo.java:19)
	at java.lang.Thread.run(Thread.java:748)

所以 Object 类中的 wait、notify、notifyAll 方法用于线程等待和唤醒的方法,都必须在synchronized内部执行 (必须用到关键字synchronized)。

  • 异常二:当notify()运行在wait()前面时【放开10~14行代码】,线程无法被唤醒,程序无法正常执行;

这个不用测试我们都知道,放这里是为了突出后面LockSupport中的park()unpark()可以这么干;

1.2 await 和 signal 方法

public static void main(String[] args) {

    // 第一个线程调用 await()方法
    new Thread(()->{
        /*try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ":进入并等待");
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":被唤醒");
        }finally {
            lock.unlock();
        }
    },"A").start();

    // 第二个线程调用 signal()方法,唤醒线程A
    new Thread(()->{
        lock.lock();
        try {
            condition.signal();
            System.out.println(Thread.currentThread().getName() + ":通知");
        }finally {
            lock.unlock();
        }
    },"B").start();
}

正常返回:

A:进入并等待
B:通知
A:被唤醒
  • 异常一:如果把两个线程的加锁释放锁的方法去掉【10,20,26,31行】,抛出异常:
A:进入
Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
	at com.beef.lockSupport.LockSupportDemo2.lambda$main$1(LockSupportDemo2.java:38)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
	at com.beef.lockSupport.LockSupportDemo2.lambda$main$0(LockSupportDemo2.java:24)
	at java.lang.Thread.run(Thread.java:748)

所以await()signal()方法用于线程等待和唤醒的方法,必须在lock()unlock()

  • 异常二:当await()运行在signal()前面时【开放第5~9行代码】,线程无法被唤醒,程序也无法正常执行;

小总结

传统的synchronizedLock实现等待唤醒通知的约束:

  • 线程先要获得并持有锁,必须在锁块(synchronizedlock)中;
  • 必须要先等待后唤醒,线程才能够被唤醒;

1.3 park 和 unpark 的方法

public class LockSupportDemo3 {

    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {

        // 第一个线程调用 park()方法
        Thread a = new Thread(() -> {
            /*try {
                TimeUnit.SECONDS.sleep(1L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            System.out.println(Thread.currentThread().getName() + ":进入并加锁");
            // 阻塞,等待唤醒
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + ":唤醒");
        }, "A");
        a.start();

        // 第二个线程调用 unpark()方法,唤醒线程A
        new Thread(()->{
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+":解锁");
        },"B").start();
    }
}

正常返回:

A:进入
B:通知
A:被唤醒

unpark()运行在park()前面时【开放第10~14行代码】,程序可以正常执行;

B:解锁
A:进入并加锁
A:唤醒

这种情况相当于第12行LockSupport.park();被注释掉了;

小总结

LockSupport中的park()unpark()无锁块要求,且可以先唤醒后等待;


2. LockSupport 源码

LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。LockSupport 调用的 Unsafe 中的 native 代码,是通过C语言原语级别来控制的;

比如我们看下park()方法:

public static void park() {
    UNSAFE.park(false, 0L);
}

// 点进UNSAFE.park方法
public native void park(boolean var1, long var2);
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值