Java并发编程---线程间的通信基础篇

线程间的通信可以分为文件共享、网络共享、共享变量、JDK提供的线程协调API(suspend/resume、wait/notify、park/unpark),今天我们着重来讲一下JDK提供的线程协作的API。

suspend/resume

suspend/resume方式的线程间协作时容易产生死锁,所以已经被JDK所废弃使用。(典型的生产者消费者模型中,生产者在占用了lock1锁之后,进行了suspend挂起操作,而消费者此时需要拿到lock1锁之后才能生产包子,那么此时就会产生死锁的现象;另外suspend方法要早于resume执行,否则也会产生死锁)

wait/notify

wait/notify的操作需要在同步代码块里边进行调用,否则会抛出异常(IllegalMonitorStateException)。wait方法会使当前线程进入等待状态,并加入该锁对象的等待集合中,并放弃当前持有的对象锁。notify/notifyAll方法是用来唤醒一个或者所有在等待该对象锁的线程。注意:wait方法虽然会自动解锁,但有顺序要求,它也要早于notify方法,否则会一直等待下去。

park/unpark

这两个方法是LockSuport提供的方法。线程调用park则等待“许可”,调用unpark则为指定线程提供“许可”,也即unpark(Thread thread)方法是需要有一个指定的线程参数的。这里的“许可”类似于一个已经存在的令牌,谁拿到令牌,谁就可以进行运行。对于park/unpark来说,这两个方法的执行没有先后顺序。他们不需要在同步代码块执行,一旦两个线程中,有一个线程在拿到了lock1锁之后,进行了park()的调用,那么其他线程如果再想要拿到lock1进行unpark操作就会拿不到lock1,从而形成死锁。
示例代码如下:

public class ParkUnparkDemo {
    //包子铺对象
    volatile Object bozipu = null;
    //资源锁
    Object baozi_lock = new Object();

    public static void main(String[] args) throws Exception{
        System.out.println("主线程开始运行");
        new ParkUnparkDemo().parkUnparkTest();
        //new ParkUnparkDemo().parkUnparkExceptionTest();
        //new ParkUnparkDemo().moreParkTest();
//        new ParkUnparkDemo().moreUnparkTest();

    }

    /**
     * 测试park/unpark方法的使用
     */
   public  void parkUnparkTest() throws Exception {
        //开启消费者线程1:等待包子铺有包子之后,进行消费买包子
       Thread consumer =new Thread(() -> {
           //如果包子铺没有开业,则等待包子铺开业的"许可"
           System.out.println("等待包子铺开张。。。");
           //这里使用while进行是否被唤醒,不要使用if,因为会有伪唤醒的状态
           while (bozipu == null){
               LockSupport.park();
               System.out.println("包子已经买到,可以回家了!");
           }
       });
       consumer.start();

       //等待3秒钟开始创建包子铺
       Thread.sleep(3000L);
       bozipu = new Object();
       //给线程consummer颁发许可
       LockSupport.unpark(consumer);
       System.out.println("已经通知消费者包子铺开张");
   }

    /**
     * park/unpark方法异常情况测试
     */
   public   void parkUnparkExceptionTest() throws Exception{
       //开启消费者线程1:等待包子铺有包子之后,进行消费买包子
       Thread consumer =new Thread(() -> {
           //如果包子铺没有开业,则等待包子铺开业的"许可"
           System.out.println("等待包子铺开张。。。");
           if(bozipu == null){
               //拿到baozi_lock锁
               synchronized (baozi_lock) {
                   LockSupport.park();
                   System.out.println("包子已经买到,可以回家了!");
               }
           }
       });
       consumer.start();

       //等待3秒钟开始创建包子铺
       Thread.sleep(3000);
       bozipu = new Object();
       //此时因为baozi_lock锁已经被消费者占有,无法继续执行
       synchronized (baozi_lock){
           //给线程consummer颁发许可
           LockSupport.unpark(consumer);
           System.out.println("已经通知消费者包子铺开张");
       }
   }

    /**
     * 多次调用unpark方法,调用一次park方法,线程会继续运行
     */
   public void moreUnparkTest(){
       LockSupport.unpark(Thread.currentThread());
       LockSupport.unpark(Thread.currentThread());
       LockSupport.unpark(Thread.currentThread());
       System.out.println("调用了三次unpark");
       LockSupport.park(Thread.currentThread());
       System.out.println("调用了一次park");
   }

    /**
     * 多次调用park方法,调用一次unpark方法,线程会进入等待状态
     */
    public void moreParkTest(){
        LockSupport.park(Thread.currentThread());
        System.out.println("调用了一次park");

        LockSupport.park(Thread.currentThread());
        LockSupport.park(Thread.currentThread());
        System.out.println("又调用了两次park");
        LockSupport.unpark(Thread.currentThread());
        System.out.println("调用了一次unpark方法");
    }
}

注意:在判断线程是否进入等待状态的时候,官方建议使用while来进行循环判断,因为处于等待中的线程可能会收到错误警报伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足条件的情况下退出。
伪唤醒是指,不是因为notify、notifyAll、unpark等api调用而唤醒,是因为更底层的原因。

猜你喜欢

转载自www.cnblogs.com/mr-ziyoung/p/13388315.html