JAVA线程通信

使用场景

要想实现多个线程之间的协作,如:线程执行的先后顺序、获取某个线程执行的结果等等,就需要使用到线程通信,例如:生产者-消费者模型、线程阻塞线程唤醒等场景。

JDK提供的线程协调API

细分为:suspend()/resume()(已过时)、wait()/notify()、park()/unpark().

生产者-消费者模型案例

消费者线程 商店 生产者线程 你好!今天有货吗? 抱歉,现在没货,再等等 进货啦! 通知消费者,有货啦 . 快来买东西了! 买买买... 消费者线程 商店 生产者线程

suspend()/resume()

suspend()挂起目标线程,resume() 恢复线程执行,该方式已被弃用。
该方式已被弃用主要原因是:
1、suspend()挂起线程,在导致线程暂停的同时,并不会去释放任何锁资源。其他线程都无法访问被它占用的锁。直到对应的线程执行 resume() 方法后,被挂起的线程才能继续,从而其它被阻塞在这个锁的线程才可以继续执行。
2、如果 resume() 操作出现在 suspend() 之前执行,那么线程将一直处于挂起状态,同时一直占用锁,这就产生了死锁。而且,对于被挂起的线程,它的线程状态还是 Runnable。
使用案例:

public class Demo {
    //商店
    public static Object shop = null;

    //正常的suspend/resume
    public void suspendResume() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            // 如果没商品,则进入等待
            while (shop == null) {
                System.out.println("1、进入等待");
                Thread.currentThread().suspend();
            }
            System.out.println("2、买到商品,回家");
        });
        consumerThread.start();
        // 3秒之后,生产商品
        Thread.sleep(3000L);
        shop = new Object();
        System.out.println("3、通知消费者");
        consumerThread.resume();
    }
        public static void main(String[] args) throws Exception {
        new Demo().suspendResume();
    }
} 

执行结果:
JAVA线程通信_第1张图片

wait()/notify()

这些方法只能由同一对象锁的持有者线程调用,也就是写在同步代码块里面,否则会抛出illegalMonitorStateException异常。
wait()方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。notify()方法唤醒一个(随机)正在等待这个对象锁的线程,notifyAll()唤醒所有正在等待这个对象锁的线程。
注意:虽然会wait()自动解锁,但是对顺序有要求,如果在notify()被调用之后,才开始调用wait()方法的调用,线程会永远处于waiting状态。
使用案例:

  public void waitNotify() throws Exception {
        Object obj =new Object();
        // 启动线程
      Thread wait=  new Thread(() -> {
            synchronized (obj) {
                // 如果没商品,则进入等待
                while (shop == null) {
                    try {
                        System.out.println("1、进入等待");
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("2、买到商品,回家");
        });
          wait.start();
        // 3秒之后,生产一个商品
        Thread.sleep(3000L);
        shop = new Object();
        synchronized (obj) {
            obj.notify();
            System.out.println("3、通知消费者");
        }
    }

执行结果:
JAVA线程通信_第2张图片

park()与unpark()

线程调用park则等待“许可”,unpark 方法为制定线程提供“许可(permit)”
不要求park和unpark方法的调用顺序。多次调用unpark之后,再调用park,线程会直接运行。但不会叠加,也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。

 public void parkUnpark() throws Exception {
      // 启动线程
      Thread consumerThread = new Thread(() -> {
          // 如果没商品,则进入等待
          while (shop == null) {
              System.out.println("1、进入等待");
              LockSupport.park();
          }
          System.out.println("2、买到商品,回家");
      });
      consumerThread.start();
      // 3秒之后,生产一个商品
      Thread.sleep(3000L);
      shop = new Object();
      LockSupport.unpark(consumerThread);
      System.out.println("3、通知消费者");
  }

调用结果:
JAVA线程通信_第3张图片
JDK官方建议应该在循环中检查等待条件,不要用if,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足条件的情况下退出。伪唤醒是指线程并非因为notify\notifyall、unpark 等API调用而唤醒,是更底层的原因导致的。

//wait
synchronized(obj){
while(条件判断)
obj.wait();
...
}

//park
while(条件判断){
LockSupport.park();
...
}

总结

java线程协作的三种API对比如下:

API 有同步锁是否释放锁 是否有顺序影响
suspend()/resume() 不释放
wait()/notify() 释放
park()/unpark() 不释放 无(但是至少一次调用unpark())

你可能感兴趣的:(java线程通信,多线程,java,java,多线程)