synchronized void f() { /* ... */ }
synchronized void g(){ /* ... */ }
每个对象都含有一个单一的锁(也称为监视器),这个锁本身就是对象的一部分(你不用
写任何特殊代码)。当你在对象上调用其任意 synchronized 方法的时候,此对象都被加
锁,这时对象上的其它synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。在上个例子里,如果对对象调用了f( ),对于这个对象就只能等到f( )调用结束并释放了锁之后,才能调用g( )。所以,对于某个对象,其所有synchronized方法共享同一个锁,这能防止多个线程同时访问对象所在的内存。
2.一个线程可以多次获得对象的锁。如果一个方法在同一个对象上调用了第二个方法,后者 又调用了同一对象上的另一个方法,就会发生这种情况。JVM负责跟踪对象被加锁的次数。 如果一个对象被解锁,其计数为 0。在线程第一次给对象加锁的时候,计数变为 1。每次线 程在这个对象上获得了锁,计数都会增加。显然,只有首先获得了锁的线程才能允许继续 获取多个锁。每当线程离开一个synchronized方法,计数减少,当计数为零的时候,锁 被完全释放,此时别的线程就可以使用此资源。
3.针对每个类,也有一个锁(作为类的Class对象的一部分),所以synchronized static 方法可以在类的范围内防止对静态数据的并发访问。
4.在有关Java线程的讨论中,一个常被提到的认识是“原子操作不需要进行同步制”。“原 子操作”(atomic operation)即不能被线程调度机制中断的操作;一旦操作开始,那么它一定可以在可能发生的“上下文切换”(context switch)之前(切换到其它线程执 行)执行完毕。
5.还有一个常被提到的知识是,如果问题中的变量类型是除long或double以外的基本类型, 对这种变量进行简单的赋值或者返回值操作的时候,才算是原子操作。不括long和 double的原因是因为它们比其它基本类型要大,所以JVM不能把对它的读取或赋值当成是 单一原子操作(也许JVM能够这么做,但这并不能保证)。然而,你只给long或double 加上volatile,操作就是原子的了。
6.serialNumber++;如果你有C++或其它低级语言 的背景,你可能认为自增加操作是一个原子操作,因为它通常可以用一条微处理器指令实 现。然而,在JVM中的自增加操作并不是原子的,它牵涉到一次读和一次写,所以即使在这 样简单的操作中,也为线程出问题提供了空间。
7.private static volatile int serialNumber = 0;serialNumber字段标记成volatile,其原因是每个线程都可能拥有一个本地栈以维护 一些变量的复本。如果把一个变量定义成volatile,就等于告诉编译器不要做任何优化, 这些优化可能会移除那些使字段与线程里的本地数据复本保持同步的读写操作。
1 。如果你要对类中的某个方法进行同步控制,最好同步所有方法。如果你忽略 了其中一个,通常很难确定这么做是否会有负面影响。
2。当去除方法的同步控制时,要非常小心。通常这么做是基于性能方面的考虑, 但在JDK1.3 和JDK1.4 中,同步控制所需的负担已经大大减少。此外,你只应 该在使用了性能评价工具证实了同步控制确实是性能瓶颈的时候,才能这么做。
synchronized (syncObject) {
// This code can be accessed
// by only one thread at a time
}
这也被称为 “ 同步控制块 ” ( synchronized block ),在进入此段代码前,必须得到 syncObject 对象的锁。如果其它线程已经得到这个锁,那么就得等到锁被释放以后,才 能进入临界区。
通过使用同步控制块而不是对整个方法进行同步控制,可以使多个线程访问对象的时间性能得到显著提高
同步控制块必须指定一个对象才能进行同步,通常,最合理的对象就是在其上调用方法的 当前对象: synchronized(this) ,在 PairManager2 中采用了这种方法。这样,当为同步控制块请求锁的时候,对象的其它同步控制方法就不能被调用了。所以其效果不过是缩小了同步控制的范围。
采用同步控制块进行同步,所以对象不加锁的时间更长。这也是宁愿使用同步控制块而不是对整个方法进行同步控制的典型原因:使得其它线程能更多地访问(在安全的情况下尽可能多)。