同步模式之保护性暂停

110 阅读3分钟

定义

同步模式之保护性暂停即Guarded Suspension,用在一个线程等待另一个线程的执行结果。

有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者) JDK中,join的实现、Future的实现,采用的就是此模式。

image.png

实现

@Slf4j
public class Test {

    public static void main(String[] args) {

        GuardedObject guardedObject = new GuardedObject();
        //线程一等待线程二的任务
        new Thread(() -> {
            //等待结果
            String result = (String) guardedObject.getResponse();
            log.info("t1领取到的奖状是{}", result);
        }, "t1线程").start();

        //线程二的任务
        new Thread(() -> {
            log.info("t2线程去帮t1线程领奖");
            guardedObject.setResponse(new String("三好学生奖状"));
        }, "t2线程").start();
    }
}

class GuardedObject {
    //结果
    private Object response;

    //获取结果的方法
    public Object getResponse() {
        synchronized (this) {
            while (response == null) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return response;
    }

    //生产结果的方法
    public void setResponse(Object response) {
        synchronized (this) {
            this.response = response;
            this.notify();
        }
    }
}

Park Unpark

该方法存在于LockSupport类当中,park()unpark()的作用分别是阻塞线程和解除阻塞线程。

三种让线程等待和唤醒的方法:

  • 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程。
  • 使用JUC包中Conditionawait()方法让线程等待,使用signal()方法唤醒线程。
  • LockSupport 类的park()unpark()可以阻塞当前线程以及唤醒指定被阻塞的线程。
@Slf4j
public class Test {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("start...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("执行阻塞park方法...");
            LockSupport.park();
            log.debug("线程一执行park方法完毕...");
        });
        t1.start();
        Thread.sleep(1000);
        log.debug("解除阻塞t1线程的unpark方法");
        LockSupport.unpark(t1);
    }
}

//执行结果
18:31:04.489 [Thread-0] DEBUG com.wuke.test.Test - start...
18:31:05.488 [main] DEBUG com.wuke.test.Test - 解除阻塞t1线程的unpark方法
18:31:06.500 [Thread-0] DEBUG com.wuke.test.Test - 执行阻塞park方法...
18:31:06.500 [Thread-0] DEBUG com.wuke.test.Test - 线程一执行park方法完毕...

Objectwaitnotify相比

  • wait & notify & notifyAll必须配合synchronized一起使用,而unpark则不需要。
  • park & unpark是以线程为单位来进行阻塞与唤醒线程,而notify只能随机唤醒一个线程,notifyAll则是唤醒所有的线程,相比没那么精确。
  • park & unpark可以先unpark,而wait & notify 不能先notify

原理

LockSupport类使用了一种名为Permit(许可证)的概念来做到阻塞和唤醒线程的功能。

  • 当调用unpark()方法的时候会赋给当前线程一个Permit(许可证)。
  • 当调用park()方法的时候会判断当前线程有没有Permit(许可证),如果没有则进行阻塞。直到其拥有一个Permit(许可证)才执行,执行后将当前线程拥有的Permit许可证置空。

每个线程只有一个permit(许可证),这意味着即使我们多次执行unpark()方法,在执行一次park()方法后,该对象拥有的许可证为空。

但当多次执行park()方法,在执行一次unpark()方法后,则会阻塞。因为多次调用park()方法则需要多个许可证,而线程只有一个permit(许可证),permit(许可证)不足所以阻塞。