15、Condition(await、signalAll)和object(wait、notifyall)

1、介绍

  • 1、condition:

    一般是和锁绑定的,通过lock.newCondition()产生 可以生产多个,分别控制不同的线程角色(生产者和消费者)

  • 2、object:

    任何对象都有,一般使用临界资源(queue数组)进行控制

    由于queue只有一个,所以notifyall的时候会唤醒所有线程(生产者和消费)不能够“分类”控制

  • 3、使用共同点==>都必须先加锁,然后在操作,否则会报await被打断的异常。

  • 4、加锁锁定后,如果线程一直在运行,其他线程申请这个锁,肯定会被阻塞,但是如果此线程await之后,就会释放锁,让其他线程进来

  • 5、为什么要加锁?看总结

2、优缺点:

  • 1、Condition:

    由于可创建多个,可以精确唤醒一组线程,其他线程继续等待,免得浪费资源。 比如队列为null,则wait当前消费者,唤醒所有生产者,而不会唤醒其他消费者 因为即使唤醒其他的消费者,由于此时队列为null,也会即刻进入wait状态 同时还会进行锁竞争,损耗性能

  • 2、object:

    只有一个,同样如果队列为null时,wait当前消费者,然后notifyall(),唤醒其他所有线程(生产者和消费者)

    其他消费者,第一竞争锁,然后进来后,发现队列还是为null,立马await

    也就是没有做事情,还竞争锁,损耗性能。

  • 3、ConditionCPU负载

  • 4、ObjectCPU负载

Condition,100W ==> 5753毫秒

Object,100W ==> 5797毫秒

2、代码,注意:一定要先锁住

两种方式加锁,模拟实现消息队列

  • 1、QueueImpl.java

      import java.util.concurrent.locks.Condition;
      import java.util.concurrent.locks.ReentrantLock;
    
      /**
       * 模拟消息队列,分别使用condition和object对象进行加锁,同步控制。
       *
       * condition:
       *      一般是和锁绑定的,通过lock.newCondition()产生
       *      可以生产多个,分别控制不同的线程角色(生产者和消费者)
       *
       * object:
       *      任何对象都有,一般使用临界资源(queue数组)进行控制
       *      由于queue只有一个,所以notifyall的时候会唤醒所有线程(生产者和消费)
       *      不能够分开控制
       *
       * 使用共同点:
       *      都必须先加锁,然后在操作,否则会报await被打断的异常
       *      线程执行await、yieldhi释放锁,并且进行则塞等待
       *      sleep不会释放锁
       *
       * [@Author](https://my.oschina.net/arthor) liufu
       * [@Company](https://my.oschina.net/u/3478402) 任子行网络技术股份有限公司
       * @CreateTime 2018/4/19  9:03
       */
      public class QueueImpl<T> {
          private Object[] queue;
          private int size;                //队列数组长度
          private int msgSize = 0;         //队列中的数据量
          private int productIndex = 0;   //生产者位置
          private int consumerIndex = 0;  //消费者已经消费了的记录
    
          private ReentrantLock lock = new ReentrantLock(true);
          //用于锁定生产者,队列满的时候,notFull锁定,等待消费者唤醒
          private Condition notFull = lock.newCondition();
          //用于消费者,数组空的时候,notEmtry锁住,等待生产者唤醒
          private Condition notEmtry = lock.newCondition();
    
          public QueueImpl(int size) {
              queue = new Object[size];
              this.size = size;
          }
    
          /**
           * 生产者插入数据, await、signalAll写法
           *
           * [@param](https://my.oschina.net/u/2303379) msg
           * [@return](https://my.oschina.net/u/556800)
           */
          public boolean conditionPut(T msg) {
              lock.lock();  //必须加锁,否则会报wait会被打断异常
    
              try {
                  //之所以用while,是因为被唤醒后,队列可能又被其他生产者写满了,继续阻塞
                  while (msgSize == size) {
                      //如果放在这里,那么就是等生产者填满队列之后,在唤醒消费者
                      //notEmtry.signalAll();
                      notFull.await();
                  }
    
                  if (productIndex > 100000) {
                      productIndex = productIndex % size;
                      consumerIndex = consumerIndex % size;
                  }
                  queue[productIndex % size] = msg;
                  productIndex++;
                  msgSize++;
    
                  //放在这里,那么就是生产一条,消费一条
                  notEmtry.signalAll();
              } catch (InterruptedException e) {
                  e.printStackTrace();
                  return false;
              } finally {
                  lock.unlock();
              }
              return true;
          }
    
          /**
           * 消费者拿取数据, await、signalAll写法
           *
           * @return
           */
          public T conditionTake() {
              T result = null;
              lock.lock();  //必须加锁,否则会报wait会被打断异常
    
              try {
                  //之所以用while,是因为队列可能又被其他线程消费完了,继续阻塞
                  while (msgSize == 0) {
                      //放在这里,表示把数据都消费完了,在唤醒生产者进行生产
                      //notFull.signalAll();
                      notEmtry.await();
                  }
    
                  result = (T) queue[consumerIndex % size];
                  consumerIndex++;
                  msgSize--;
    
                  //放在这里,消费一条,就唤醒生产者
                  notFull.signalAll();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } finally {
                  lock.unlock();
              }
              return result;
          }
    
          /**
           * 生产者插入数据, wait、notifyall写法
           *
           * @param msg
           * @return
           */
          public boolean objectPut(T msg) {
              synchronized (queue) {  //必须加锁,否则会报wait会被打断异常
                  try {
                      //之所以用while,是因为被唤醒后,队列可能又被其他生产者写满了,继续阻塞
                      while (msgSize == size) {
                          //写在这里,表示队列写满了,才唤醒消费者消费
                          //queue.notifyAll();
                          queue.wait();
                      }
    
                      if (productIndex > 10000) {
                          productIndex = productIndex % size;
                          consumerIndex = consumerIndex % size;
                      }
                      queue[productIndex % size] = msg;
                      productIndex++;
                      msgSize++;
    
                      //写在这里,表示队列写满了,生产一条,就唤醒消费者消费
                      queue.notify();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                      return false;
                  }
              }
              return true;
          }
    
          /**
           * 消费者拿取数据, wait、notifyall写法
           *
           * @return
           */
          public T objectTake() {
              T result = null;
              synchronized (queue) {    //必须加锁,否则会报wait会被打断异常
                  try {
                      //之所以用while,是因为队列可能又被其他线程消费完了,继续阻塞
                      while (msgSize == 0) {
                          //写在这里,表示消费者把队列中的信息全部消费了,才唤醒生产者生产
                          //queue.notifyAll();
                          queue.wait();
                      }
    
                      result = (T) queue[consumerIndex % size];
                      consumerIndex++;
                      msgSize--;
    
                      //写在这里,表示消费一条,就唤醒生产者进行生产
                      queue.notify();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
              return result;
          }
      }
    
  • 2、ProductAndConsumer.java

      import java.util.concurrent.atomic.AtomicInteger;
    
      /**
       * Condition测试
       * Condition一般是和锁绑定的,通过lock.newCondition()产生
       * Condition只能锁定和唤醒相同相同condition对象的值
       * <p>
       * 本例子实现消费者和生产者对一个数组进行生产和消费操作
       *
       * @Author liufu
       * @CreateTime 2018/4/18  20:31
       */
      public class ProductAndConsumer {
    
          public static void main(String[] args) {
    
              QueueImpl<String> queue = new QueueImpl<String>(200);
              AtomicInteger atomicInteger = new AtomicInteger(0);
    
              //condition生产者
              new Thread(() -> {
                  while (true) {
                      queue.conditionPut("i am msg:" + atomicInteger.getAndIncrement());
                  }
              }).start();
    
              //condition消费者
              new Thread(() -> {
                  while (true) {
                      System.out.println(queue.conditionTake());
                  }
              }).start();
    
    
    
              //=============================================================
    
    
              //object生产者
              new Thread(() -> {
                  while (true) {
                      queue.objectPut("i am msg:" + atomicInteger.getAndIncrement());
                  }
              }).start();
    
              //object消费者
              new Thread(() -> {
                  while (true) {
                      System.out.println(queue.objectTake());
                  }
              }).start();
          }
      }
    

3、总结

  • 1、必须加锁,lock.lock()或者synchronized (queue)。否则会报线程被打断异常

  • 2、线程调用wait()之后,会释放已经获得的锁给其他线程进来,并且将其自身放置在对象的等待集中,等待notify或者notifyall

  • 3、那么问题来了:大家都在wait,如果执行了notifyall,所有线程立马同时执行,不就会导致线程不安全吗?

    答案是:不会,因为

    notifyall确实是通知了所有执行此对象wait而等待的线程,他们都会被唤醒,但是执行之前它们都需要重新竞争获得锁(这就是为什么要加锁的原因)。这就限制了同一时刻,只会有一个线程获得锁执行,其他线程继续等待。拿到锁后,会从上一次wait()方法之后执行(恢复现场)。

    也就是说必须满足两个条件,缺一不可

      1、被notify到
      2、重新获得锁
    
  • 4、notify和notifyall有什么区别?

    notify只会唤醒等待中的一个线程,其他的不会被唤醒,这个时候唤醒的线程执行完成后,必须继续调用notify方法唤醒另外一个线程。

    而notifyall会唤醒所有等待线程,只是由于有“锁”的原因,只有一个线程执行,其他线程继续阻塞,只是这个时候不需要再继续notify而已。

    所以什么时候用notifyall,什么时候用notify,就要看情况了。

  • 5、NotifyallTest.java

      import java.util.concurrent.atomic.AtomicInteger;
    
      /**
       * 主要是为了测试wait和notifyall到底是什么原理
       *
       * @Author liufu
       * @CreateTime 2018/5/9  10:20
       */
      public class NotifyallTest {
          public static void main(String[] args) throws InterruptedException {
              String[] queue = new String[100];
              AtomicInteger atomicInteger = new AtomicInteger(0);
              for (int i = 0; i < 10; i++) {
                  new Thread(() -> {
                      int index = atomicInteger.getAndIncrement();
                      synchronized (queue) {
                          System.out.println(String.format("thread : %s wait", index));
                          try {
                              queue.wait();
                              System.out.println(String.format("thread : %s runing, 10秒内没有其他线程运行!", index));
                              Thread.sleep(10000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }).start();
              }
    
              //让上面的所有线程都启动,在notifyall
              Thread.sleep(100);
    
              //这个也要加锁
              synchronized (queue) {
                  queue.notifyAll();
              }
          }
      }
    

转载于:https://my.oschina.net/liufukin/blog/2222556

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值