一、竞态条件&临界区
在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。如,同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件。实际上,这些问题只有在一或多个线程向这些资源做了写操作时才有可能发生,只要资源没有发生变化,多个线程读取相同的资源就是安全的。
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。上例中add()方法就是一个临界区,它会产生竞态条件。在临界区中使用适当的同步就可以避免竞态条件。
二、详解Java同步
Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。
1、实例方法同步
public synchronized void add(int value){
this.count += value;
}
Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。
2、静态方法同步
public static synchronized void add(int value){
count += value;
}
静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。
3、实例方法中的同步块
public synchronized void add(int value){
synchronized (this) {
count += value;
}
}
示例使用Java同步块构造器来标记一块代码是同步的。该代码在执行时和同步方法一样。
示例中使用了“this”,即为调用add方法的实例本身。在同步构造器中用括号括起来的对象叫做监视器对象。上述代码使用监视器对象同步,同步实例方法使用调用方法本身的实例作为监视器对象。
一次只有一个线程能够在同步于同一个监视器对象的Java方法内执行。
在下面的两个方法的示例中,同步都是在他们所调用的实例对象上,所以每次只有一个线程能够在两个同步块中任意一个方法内执行。
public synchronized void log1(String msg1, String msg2) {
System.out.println(msg1 + "-" + msg2);
}
public void log2(String msg1, String msg2) {
synchronized (this) {
System.out.println(msg1 + "-" + msg2);
}
}
4、静态方法中的同步块和上面类似,下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。
public static synchronized void log1(String msg1, String msg2) {
System.out.println(msg1 + "-" + msg2);
}
public static void log2(String msg1, String msg2) {
synchronized (MyTest.class) {
System.out.println(msg1 + "-" + msg2);
}
}
这两个方法不允许同时被线程访问。
如果第二个同步块不是同步在ThisClass.class这个对象上。那么这两个方法可以同时被线程访问。
三、参考