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详解
2. java.lang.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 这个对象对所有线程都可见,专门用来协调多线程并发的情况。