1. synchronized
的作用
synchronized
类似于win32中的临界区,临界区的作用:对于共享的全局变量,在多个线程并发的情况下,对这样的全局变量的读写就会发生读写的冲突,
使得全局变量的读写,能够以原子的方式被执行,而不是一个线程要读取全局数据时候,由于线程调度,而另一个线程则此时被唤醒,改变了这个全局变量的值,
这样使得读取的线程获得的数据不稳定,所以对于全局变量的读写的代码,我们要使用临界区使得这些代码原子化,此时只要在临界区中的代码,就会以原子方式执行,
而不会由于线程调度被中断,也就保证了全局共享资源的互斥访问。例如:
CRITICAL_SECTION CriticalSection; // 临界区对象
1 DWORD WINAPI WriteThreadProc( LPVOID lpParameter ) 2 { 3 // 进入临界区 4 EnterCriticalSection(&CriticalSection); 5 // 写资源 6 g_data = 123; 7 // 离开临界区 8 LeaveCriticalSection(&CriticalSection); 9 }
DWORD WINAPI ReadThreadProc( LPVOID lpParameter ) { // 进入临界区 EnterCriticalSection(&CriticalSection); // 读资源 printf(g_data); // 离开临界区 LeaveCriticalSection(&CriticalSection); }
上面的例子中的在临界区中,进行数据读写,就不会发生共享资源冲突。类似的,在java中使用 synchronized
关键字来实现临界区代码块。
关于的synchronized
使用方法,参考:Java synchronized详解
Java:使用synchronized和Lock对象获取对象锁
2. java.lang.Object.notify 方法
每一个对象都有一个所谓的 object's monitor(对象监视器)。假设其为:obj.monitor。能够获得obj对象的引用的线程都可以通过 obj.wait() 方法在这个对象监视器(obj.monitor)上注册一个监听。这样所有调用 obj.wait的线程都将添加到,对象的类似等待队列中。
JDK文档翻译:
唤醒在此对象监视器上等待的单个线程。如果有多个线程都在此对象上等待(也就是线程中执行了 obj.wait),则会选择唤醒其中一个线程。选择是任意性(随机的)的,并在对实现做出决定时发生。线程通过调用其中一个 wait
方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器(obj.monitor)的所有者:
- 通过执行此对象的同步实例方法。(也就是方法签名上有
synchronized
关键字的实例方法,也就是执行 obj.instanceMethod ,并且这个方法有synchronized
修饰) - 通过执行在此对象上进行同步的
synchronized
语句的正文。(synchronized
代码块, 也就是synchronized
(obj){obj.wait();} 这样这个代码块中就具有了 obj.monitor,就可以调用 obj.wait 了) - 对于
Class
类型的对象,可以通过执行该类的同步静态方法。(也就是方法签名上有synchronized
关键字的实例方法,也即类级方法,也就是obj.staticMehtod,并且这个方法的签名有synchronized
关键词修饰)
一次只能有一个线程拥有对象的监视器。
在上面三种情况下,线程就拥有了对象监视器(obj.monitor)则就可以在其中调用 obj.notify 方法了。
线程可以成为此对象监视器(obj.monitor)的所有者:的第一种情况 同步对象就是 this 当前的实例对象。所以在这个实例方法中就可以这样调用:this.notify();
第三种情况下,其实也就是同步对象就是 ThreadTest.class.wait(); 也就是在类对象上调用 notify 方法。
第二种情况下,由于我们可以手动的指定指定我们要获得的监视器的对象,此时就不仅仅是 this 和 ClassName.class 这两个对象了,可以是其他的任何对象,例如 waitObj 这个对象对所有线程都可见,专门用来协调多线程并发的情况。
3. java.lang.Object.wait 方法
Thread.sleep方法:在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。也就是说如果,sleep方法在 synchronized(obj){} 代码块中被执行的话是不会失去对obj对象的监视器所属权。也就是说其他线程中的 synchronized 是不会获得
obj对象的监视器所属权。
java.lang.Object.wait 方法和 sleep方法有点类似,但是还大有不同。
他也会导致当前线程处于等待状态。但是对于调用这个方法的线程必须拥有此对象监视器。否则调用会出现 IllegalMonitorStateException
也就是说对于 wait 方法的调用也必须是在 synchronized 代码块中,否则就会出问题。JDK文档中关于这个方法非常关键的一句话:
This method causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object.
这个方法(obj.wait方法)将导致当前线程(wait方法调用所发生的线程)被放置到调用对象(obj)的等待集合中(有可能多个线程在obj这个对象上等待)并且导致当前线程(the curent thread)放弃在这个对象上的所有同步请求(即:此时这个线程将不会再占有对象obj的对象监视器,也就是其他想通过 synchronized(obj){} 来获得对象obj的
对象监视器线程就将有可能获得obj对象的监视器,其同步代码就就会被执行了)。
总结一下:当调用 obj.wait方法时发生三件事:
① 把当前线程放置到对象obj的等待集合中
② 释放当前线程中 synchronized(obj){} 这个代码块中对 obj 对象的锁的占用,从而使得其他请求 obj 对象同步(通过synchronized(obj){}代码块)的线程有机会获得obj对象锁(对象监视器)使其同步代码块得以执行。
③ 使得当前线程处于 sleep 状态,直到有下面几种情况发生:
wait方法返回的机制:
- Some other thread invokes the notify method for this object and thread T happens to be arbitrarily chosen as the thread to be awakened.(其他线程在obj对象上调用了 notify 或者 notifyAll方法,则wait方法就返回了,也就是线程被唤醒了,可以被调度了)
- Some other thread invokes the notifyAll method for this object.
- Some other thread interrupts thread T.(异常返回,其他的线程对该线程调用了interrupt方法,使得当前线程收到了 InterruptedException ,从而使得线程唤醒,)
- The specified amount of real time has elapsed, more or less. If timeout is zero, however, then real time is not taken into consideration and the thread simply waits until notified.(wait 函数的重载版本中,可以输入等待时间,如果超过这个时间,则等待就被返回了,此时的线程也被唤醒了)。
上面的这几种情况发生时:The thread T is then removed from the wait set for this object and re-enabled for thread scheduling.(当前线程将从之前的等待对象obj的等待集合中被移出,然后重新处于可调度状态,注意是处于可调度状态(有可能obj锁还没有被释放),而不是线程立即执行,唤醒的线程是否被立即执行,则取决于线程调度程序)。此时,该线程以常规方式与其他线程竞争,以获得在该对象(obj)上同步的权利;一旦获得对该对象的控制权,该对象上的所有其同步声明都将被恢复到以前的状态,这就是调用 wait 方法时的情况。然后,线程 T 从 wait 方法的调用中返回。所以,从 wait 方法返回时,该对象和线程 T 的同步状态与调用 wait 方法时的情况完全相同。返回之后,线程就开始继续执行,直至到同步代码块结束。假设线程M执行了obj.notify方法,则此时线程M必然拥有对象Obj的控制权,同时对于 notify他不是一个阻塞的方法,所以此时线程M的同步代码块将继续被执行,假设obj.notify下面还有很多代码:例如
1 function some(){ 2 3 synchronized(obj){ 4 5 obj.notify(); 6 7 // 由于此时是在同步代码块中,所以下面的方法调用不会被中断 8 9 // 而是顺序执行,知道这个代码块被执行完毕 10 11 dosomthing(); 12 13 doanothing(); 14 15 dothatthing(); 16 17 } 18 19 // 这个方法在同步代码块的外面,则 20 21 // 线程就很有可在此中断, 22 23 doit(); 24 25 }
对于 wait 方法 和 notify 方法的调用,JDK文档中有这样的备注描述:
wait: This method (wait)should only be called by a thread that is the owner of this object's monitor. See the notify
method for a description of the ways in which a thread can become the owner of a monitor.
notify:This method should only be called by a thread that is the owner of this object's monitor.
其实也就是说,这两个方法调用通常都以下面的代码模型出现:
1 synchronized(obj){ 2 obj.wait(); 3 } 4 5 synchronized(obj){ 6 obj.notify(); 7 }
即用obj做同步对象,同时也在其上做等待和通知。