synchronized, wait and notify 

最近在J2ME平台上写一个UI框架库,因为自己之前Java的经验并不是很多,比较熟悉的是Qt的那一套,搞了半天才发现Java的Object本身就可以作为互斥体和简单的信号量使用,利用sychronized关键字和Object的wait和notify方法(wait的语义非常的复杂,直接从字面上去理解很难明白它实际的作用),就已经可以完成大部分多线程编程的需要(在这里不得不膜拜一下Java语言的设计者…)。

 

synchronized

synchronized在Java中可以用来为当前运行的线程获得一个对象的监视器(monitor),或者简单地说获得一个对象的所有权。因为一个时间只能有一个线程可以获得对象的所有权,假设对象A已经被线程A获得,那么其它试图获得对象A的线程将会被阻塞,等待对象A被线程A释放。

如果直接用synchronized来修饰方法,那么某个时刻调用该方法的线程所要获得的对象就是当前对象(this),如果synchronized用来修饰静态方法,那么线程所要获得的对象就是这个类的类对象(Class)。

如果线程已经获得对象的所有权,它再次对该对象发起请求是可以的,但不会有任何效果也不会有什么副作用,可能只是简单增加一个计数值用于处理嵌套的状况。当线程退出一个synchronized块的时候,它进入这个块所需要获得的那个对象的所有权将会被自动释放,从而允许其它竞争该对象的线程可以获得对象的所有权。

 

Object.wait

如果一个线程已经获得某个对象的所有权时,它也可以暂时选择放弃而不用等到退出对应的synchronized块,线程可以调用对象的wait方法放弃对象的所有权并把自己置于等待状态,在该状态下线程无法被执行和调度直到被其它线程通知,或者被中断,或者指定的timeout时间已经耗尽。

当线程从等待状态中被唤醒重新参加调度和执行时,并不意味着它可以马上跳过wait运行下去,实际上,它需要参与之前放弃的对象的竞争直到它重新获得该对象的所有权为止,当它重新获得时,对于该线程而言,一切又恢复到了之前的状态,这个时候它从wait方法中返回,并带着对象的所有权继续执行下去直到对应的synchronized块的结束。

所以理解wait方法的关键在于,它是用于暂时放弃对象的所有权,并在等待某段时间后重新参与该对象所有权的竞争。而timeout参数可以用来控制等待的时间,如果timeout为0,线程会被置于等待状态直到被其它线程通知或者线程被系统中断,这里潜在的危险在于如果没有一个别的线程去获得这个被原线程放弃的对象并通过它去通知原线程,原线程可能永远处于等待状态而不会被唤醒,而处于等待状态的线程不会被系统调度,等于是一个dead thread。如果存在上述的状况,指定一个非0的timeout的时间是绝对必要的,这时即使没有其它线程来通知,在timeout时间后,原线程也会被自动唤醒,如果此时对象的所有权没有被别的线程占有,原线程可以马上获得这个对象并继续执行下去。

 

Object.notify and notifyAll

有了上面对wait的理解,要理解notify就比较简单了。对象的notify方法可以被一个拥有该对象的线程用来唤醒其它因为这个对象而处于等待状态的线程(它们调用了对象的wait方法),被唤醒的线程需要重新参与对这个对象所有权的竞争,所以一般notify调用都是在调用线程准备放弃该对象所有权的时刻发生(synchronized块的末尾),这样被唤醒的线程马上就能够得到对象的所有权并正常执行下去。

假设在对象上等待的线程有多个,notifyAll可以唤醒全部等待的线程,它们一起参与对对象所有权的竞争,而notify则随机唤醒其中的一个线程,其它线程仍然处于等待状态直到一个新的线程调用notify或者notifyAll。