java监视器机制与线程信号

参考链接:

http://tutorials.jenkov.com/java-concurrency/thread-signaling.html

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#wait()

https://stackoverflow.com/questions/3362303/whats-a-monitor-in-java?newreg=a13515ce10764cada49a77b1d47ebf86

https://books.google.com/books?id=-x1S4neCSOYC&printsec=frontcover&dq=java+concurrent&hl=en&ei=pHJRTL3YNIuInQe0oI3eAw&sa=X&oi=book_result&ct=result#v=onepage&q=monitor&f=false

学习小结

先说说线程的信号,不同线程之间可以通过共享变量和信号来沟通。比如一个消费者和一个生产者,生产者生产数据给消费者使用。如果通过一个共享变量来标识生产者是否已经生产了一个数据可供消费者使用,那么在未有数据被生产的时候,消费者需要在线程的循环语句中检测该共享变量,一直循环等待直到有了数据可使用。这种交流方式叫做忙等待,忙等待是很浪费cpu时间的。

因此可以通过信号来解决,如果没有数据,则消费者阻塞,直到生产者生产数据后发出信号通知等待的消费者线程。于是该消费者开始运行并处理数据。

1  monitor监视器

每一个Java对象都有一个监视器,监视器一个控制并发访问对象的一个机制,它由JVM虚拟机实现,因此你打开Object的文档看,是找不到他相关的信息的。而一个监视器包含两部分:锁(lock)和等待集合(wait sets)。当线程对对象加锁时,就是为了获得监视器的锁,当加锁的线程被阻塞时会被记录下来(我不知道被该阻塞线程会被记录在哪里,有朋友知道的可以告诉我)。而通过wait()调用而等待的线程会被记录在wait sets中。由于监视器是由虚拟机实现的,所以只能有Object的wait、notify、notifyAll和synchronized与Thread的interrupt等方法操作。

2  wait(),notify() ,notifyAll() and interrupt()

前三个是Object类的方法,后一个是Thread类的方法。

从上图的官方文档可以看出,想调用wait(),notify(),notifyAll()这个三个方法,必须要对调用该方法的对象进行加锁,调用wait的线程被等待后直到其他线程调用通过相同对象调用notify()或者notifyAll()后才能重新运行。wait()函数在等待的时候也可能会被中断(interrupt),比如调用Thread.interrupt()方法,他和notify是一样的效果,但是会抛出异常(InterruptException),从方法声明就可以看出。

文档给出里一个例子不够详细,当他解决了信号丢失(missed signals)和假唤醒(spurious wakeups)的问题,如果需要解决被interrupt中断的问题,还需要加上try语句进行异常处理。

3、信号丢失(missed signals)和假唤醒(spurious wakeups)

如果仅仅适用wait、notify等函数来进行线程之间的交流,可能会造成信号丢失的问题,比如说一个线程在等待线程调用wait之前就调用了notify,那么等待线程就不会收到通知信号,它可能会永远等待下去。所以还需要一个共享变量通过判断来约束一下。

所谓假唤醒是由于jvm是的实现机制问题,在调用wait时,可能会出现未被notify通知或中断的情况下也能自动被唤醒。因此在使用wait、notify时还要考虑假唤醒问题。

4、一个好的使用例子

为了解决信号丢失、假唤醒与中断问题,一个好的例如可以如下所写:

public class MyWaitNotify{

  MonitorObject myMonitorObject = new MonitorObject();
  boolean wasSignalled = false;

  public void doWait(){
    synchronized(myMonitorObject){
      while(!wasSignalled){
        try{
          myMonitorObject.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

  public void doNotify(){
    synchronized(myMonitorObject){
      wasSignalled = true;
      myMonitorObject.notify();
    }
  }
}

比如在生产者和消费者例子中,他们共享一个MyWaitNotify的对象,消费者调用doWait方法等待(不会和忙等待一般耗费cpu资源了),生产者生产完数据后调用doNotify方法通知消费者,从而使得消费者开始处理数据。

之前我们说过,调用wait、notify方法的线程需要拥有调用该方法的对象的(上面的例子中就是对myMonitorObject 加锁,通过myMonitorObject 调用wait、notify函数)。

为了解决信号丢失,需要一个变量来约束一下(如上述的wasSignalled )。如果生产者先调用doNotify通知消费者,但是消费者却还未进入等待状态,由于wasSignalled 被生产者置为true了,消费者调用doWait()的时候会直接跳过循环,不必等待,而是处理数据去了。

为了解决假唤醒,使用了while循环,由于是假唤醒,doNotify没有被执行过,wasSignaled仍为false,因此即使从wait中恢复运行,还是会再进进入循环,执行wait方法进去等待状态。

为了解决等待过程中出现中断的问题,使用try来处理它,虽然他执行wait()后的代码,但是由于wasSignalled 没有置为true,它还是会重复循环执行wait(),被当做了假唤醒来处理。

这只是我的一个小结,知识的跨度可能比较大,详细过程参考第一条连接,里面有详细的说明过程

5、陷阱--使用字符串常量作为监视器

Calling wait()/notify() on string constants

如图所示,修改上述代码中的myMonitorObject 对象为String类型后,并初始化为一个字符串常量:

 String myMonitorObject = "";

对于相同的字符串常量,java不会并不会新建一个变量,而是直接将已有的变量赋值给了引用变量,因此MyWaitNotify1和MyWaitNotify2指向了相同的字符串对象。这样会出现一个问题,就是其中一个线程调用了wait方法后,可以被其他线程调用notify来唤醒。这是因为他们公用了一个监视器,这是会出现一些问题。具体问题请看链接。因此最好不要使用字符串常量作为监视器。

6、监视器机制

什么是监视器机制?其实我也不太清楚,大概就是工作的具体过程和原理吧,这些过程体现在方法的实现机制上。嗯。。就是这样。。

前面已经讲述过了监视器的组成和部分原理,下面看看每个函数的实现过程:

嗯??!!怎么全是英文?。。我也没办法啊,懒得翻译。。就大概说说过程吧。

6.1 wait

  • 执行wait时,如果有中断,就直接抛出异常,否则该线程阻塞。(注意,进入wait之前需要获得监视器,也就是加锁,notify和notifyAll也是一样)。
  • 将该线程放入该监视器的wait set中
  • 释放锁该监视器的锁!!但是该线程拥有的其他锁仍保留。这是一个彻底的释放过程,即使有可重入的发生。
  • 被通知(notify)后被唤醒,然后尝试对该同步块加锁,加锁不成功则阻塞,成功就执行完毕退出wait。

6.2 notify

从该监视器的wait set中挑一个线程,唤醒他。(这个函数中没有对锁的操作,只是它处于同步块之中而已)。

6.3 notifyAll

和notify差不多,不多是将wait set中所有的线程都唤醒罢了,但是呢,他们都在同一同步块中,因此得一个一个轮流执行通过。

6.4 interrup

我们讲过了,这个也和notify差不多,不过它会抛出异常罢了,wait中有提到过。

6.5 timed wait

这又是什么鬼?就是带有时间限制的wait,规定了线程最大的等待时间,不管你是否被通知,只要你时间到了,你就得被唤醒。

note:注意哦,假唤醒和notify结果相同,都是讲线程唤醒了,如果不通过一些手段是不能区分他们的。

7、监视器原理相关的例子

下面一幅图可以清晰的看出函数里面大致的运行情况。

咳咳,只关心代码和图就好了。这里有两个同步方法,先假设一个X类的对象x,被三个线程使用,其中两个调用w()方法,另一个调用n()方法。假设T1先进入同步块,然后T2,再T3。T1和T2都进入了同步状态,之后T3调用notifyAll,等待线程全部被唤醒,但是谁先获得锁然后执行呢?不知道,都有可能,图示中只是一种可能。总之,根据第6小结讲的原理过程,可以看懂图中的执行过程,便于加深一下印象。。

over~~~

猜你喜欢

转载自blog.csdn.net/jdbdh/article/details/81873026