详细介绍了Java对象监视器的概念以及API方法,包括JDK1.5提供的Condition监视器对比。
1 对象监视器
对象监视器实际上就是与每个对象关联的Monitor对象,也叫做管程锁。实际上监视器也是一个数据结构,里面维护着一系列等待队列、同步队列等。这里不过多讨论对象监视器,详细讨论在会面的Synchronized部分中会有,本文主要针对Java初学者介绍相关的API方法,为后面的线程通信做准备,具体的应用将会在线程通信部分有讲到。
当多个线程持有的“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码!可以是实现线程同步!
关于对象监视器:Java synchronized关键字的底层实现以及锁升级优化的原理【一万字】
1.1 方法
为了方便线程协作通信,JAVA为提供了wait()和notifyAll以及notify()实现挂起线程,并且唤醒另外一个等待的线程的三个监视器方法(通知与等待)。
/**
* 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,能够使得当前线程进入等待队列,导致当前线程等待WAITING状态。该wait方法应由当前的持有对象监视器来调用。
* 想要唤醒该等待线程,也应该由其他持有相同的对象监视器的线程调用notify方法。即要求其他线程和需要被唤醒的等待线程持有相同的对象监视器。或者其他线程调用该线程的interrupt()方法, 该线程抛出InterruptedExce ption 异常返回。
* Wait的两个重载方法:
*/
public final void wait()
/**
* 限时等待timeout milliseconds(毫秒)
* @param timeout
*/
public final native void wait(long timeout)
/**
* 如果一个线程调用共享对象的该方法挂起后, 没有在指定的timeout ms 时间内被其他线程调用该共享变量的notify() 或者notifyAll() 方法唤醒,那么该函数还是会因为超时而返回。
* 如果将timeout 设置为0 则和wait 方法效果一样,因为在wait 方法内部就是调用了wait(O)。需要注意的是,如果在调用该函数时,传递了一个负的timeout 则会抛出IllegalArgumentException 异常。
*
* @param timeout
* @param nanos
*/
public final void wait(long timeout, int nanos)
/**
* 线程调用对象监视器的notify()方法,唤醒一条在该对象监视器上调用wait系列方法后被挂起的线程。如果有多条线程在同一等待队列中等待,只会选择唤醒其中一个线程,并且该选择是任意性的。
*/
public final void notify();
/**
* 调用notifyAll方法就可以唤醒对应对象监视器的等待队列当中所有的等待的线程。只对在该方法被调用之前等待的线程有效。
*/
public final void notifyAll();
1.2 特点
wait方法、notify方法、notifyAll方法,在使用的时候,必须要有自己的同步监视器(锁对象)。换句话:wait方法、notify方法、notifyAll方法,必须注册在某个同步监视器上(锁上)。(拥有自己的锁对象)否则抛出一个异常:IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
因此创建了一个锁对象,这个锁对象上就具备一组监视器方法。Wait、notify、nofityAll方法就可以使用。
为什么上面的方法定义在Object类中?
wait、notify、nofityAll 监视器方法,在使用的时候,必须要由自己的锁对象来调用。锁对象是任意对象,Java中只要是对象均有一个自己的监视器对象,任意对象都能调用的方法就应该抽出来,定义在Object 类当中,毕竟Object是所有类型的超类!
2 Condition 监视器
2.1 Object的监视器方法与Condition接口的对比
任意一个Java对象,都拥有一与之关联的唯一的监视器对象,为此Java为每个对象提供了一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。
JDK1.5出现的Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。
Object的监视器方法与Condition接口的对比如下:
Condition可以和任意的锁对象结合,监视器方法不会再绑定到某个锁对象上。使用Lock锁之后,相当于Lock 替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用。
在Condition中,Condition对象当中封装了监视器方法,并用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。
Condition的强大之处在于它可以为多个线程间建立不同的Condition,使用synchronized/wait()只有一个阻塞队列,notifyAll会唤起所有阻塞队列下的线程,而使用lock/condition,可以实现多个阻塞队列,signalAll只会唤起某个阻塞队列下的阻塞线程。
2.2 常用API方法
方法名称 | 描述 |
void await() throws InterruptedException | 当前线程进入等待状态直到被通知(signal)或中断,当前线程进入运行状态且从await()方法返回的情况,包括1.其他线程调用该Condition的signal()活signalAll()方法,而当前线程被选中唤醒 2.其他线程(调用interrupt()方法)中断当前线程 |
void awaitUninterruptibly() | 当前线程进入等待状态直到被通知,等待过程中不响应中断 |
long awaitNanos(long nanosTimeout) throws InterruptedException | 当前线程进入等待状态直到被通知,中断,或者超时。返回值表示剩余的时间,如果在nanosTimeout纳秒之前被唤醒,那么返回值就是(nanosTimeout-实际耗时)。如果返回值是0或者负数,那么可以认定已经超时了 |
boolean await(long time, TimeUnit unit) throws InterruptedException | 超时等待一段时间,如果没有通知就超时返回 |
boolean awaitUntil(Date deadline) throws InterruptedException | 当前线程进入等待状态直到被通知,中断或者到某个时间。如果没有到指定时间就被通知,方法返回true,否则,表示到了指定时间,方法返回false |
void signal() | 唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁 |
void signalAll() | 唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须获得与Condition相关联的锁 |
注意:
获取一个Condition必须通过Lock的newCondition()方法。
Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。
如果有什么不懂或者需要交流,各位可以留言。另外,希望收藏、关注一下,我将不间断更新Java各种教程!