Java 中的同步块用 synchronized 标记。同步块在Java 中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。 有四种不同的同步块:
上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。
下面是一个同步的实例方法:
public synchronized void add(int value){ this.count+= value; }
注意在方法声明中同步(synchronized )关键字。这告诉Java 该方法是同步的。
Java 实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。
静态方法同步和实例方法同步方法一样,也使用synchronized 关键字。Java 静态方法同步如下示例:
public static synchronized void add(intvalue){ count += value; }
同样,这里synchronized 关键字告诉Java 这个方法是同步的。
静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。
对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。
有时你不需要同步整个方法,而是同步方法中的一部分。Java 可以对方法的一部分进行同步。 在非同步的Java 方法中的同步块的例子如下所示:
public void add(int value){ synchronized(this){ this.count += value; } }
示例使用Java 同步块构造器来标记一块代码是同步的。该代码在执行时和同步方法一样。
注意Java 同步块构造器用括号将对象括起来。在上例中,使用了“this” ,即为调用add 方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。
一次只有一个线程能够在同步于同一个监视器对象的Java 方法内执行。
下面两个例子都同步他们所调用的实例对象上,因此他们在同步的执行效果上是等效的。
public class MyClass { public synchronized void log1(String msg1,String msg2){ log.writeln(msg1); log.writeln(msg2); } public void log2(String msg1, String msg2){ synchronized(this){ log.writeln(msg1); log.writeln(msg2); } } }
在上例中,每次只有一个线程能够在两个同步块中任意一个方法内执行。
如果第二个同步块不是同步在this 实例对象上,那么两个方法可以被线程同时执行。
同步块,示例代码如下:
public void method3(SomeObject so) { synchronized(so){ //...... } }
这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable{ private static byte[] lock = new byte[0]; // 特殊的instance变量 Public void methodA(){ synchronized(lock){ //...... } } }
注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
和上面类似,下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。
public class MyClass { public static synchronized void log1(Stringmsg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } public static void log2(String msg1, Stringmsg2){ synchronized(MyClass.class){ log.writeln(msg1); log.writeln(msg2); } } }
这两个方法不允许同时被线程访问。
如果第二个同步块不是同步在MyClass.class 这个对象上。那么这两个方法可以同时被线程访问。