Concurrence-11-Java Synchronized Blocks


Java Synchronized Blocks


##### java同步代码块

文章的地址:翻译文章的源地址

把一个方法或者一段代码标记为同步,就叫做java同步的代码块,java 同步代码块可以用来避免竞争条件 的发生。

####java同步关键字 在java中是使用关键字 synchronized 来标记同步代码块。在java中一个同步块是在某一个对象上的同步。 所有同步在同一个对象上的代码块,在某一个时刻只能有一个线程能够在其中执行代码,所有的其他的企图 进入代码块的线程都会被阻塞,知道同步代码块里面的线程离开同步快。

关键字 synchronized 可以用来标记四种类型的代码块:
1.实例方法
2.静态方法
3.实例方法中的代码块
4.静态方法中的代码块

这些块是不同的对象的同步。你需要哪种类型的同步块取决于具体情况。

####实例方法的同步 实例方法的同步代码如下:

 public synchronized void add(int value){
      this.count += value;
  }

备注:关键字在方法声明中,这样告诉java这个方法是同步的。
java中一个同步的实例方法,是在拥有这个方法实例上的同步。因此每一个实例都有同步在不同对象上的同步 方法,这里的不同对象指的拥有各自方法的实例。同步方法中只能有一个线程执行,如果多个线程存在,一个 线程在某一个时刻只能一个实例中的一个同步方法中执行代码,一个线程对应一个实例。

静态方法的同步

同步静态方法的方法就就像是同步实例方法中那样使用 synchronized关键字。下面就是例子:

  public static synchronized void add(int value){
      count += value;
  }

这里synchronized关键字,就是告诉java这个是一个同步的方法。

静态同步方法是同步在静态方法所属于的类对象上面,因为在JVM中每一个类只有一个Class对象,那么在相同 的类中,只有一个线程运行在同步的静态方法中。

如果静态方法位于不同的类中,那么每一个类中就可以有一个线程在同步静态方法中运行。每一个线程对应 一个类,不管它调用的是哪一个静态同步方法。

实例方法中的静态代码块

我们可能没有必要同步整个方法,有时候我们只需要同步方法的一部分,方法中的同步代码块就是做这个的。 下面就是一个示例:

  public void add(int value){
    synchronized(this){
       this.count += value;   
    }
  }

上面的示例就是使用同步代码来使一段代码同步,这段的代码在执行的时候就像在同步方法中一样。
备注:java同步代码块在括号里面使用了一个对象,在这个示例中,我们使用的是this,代表调用这个 add方法的实例,这个synchronized关键字括号里面使用的对象被称之为监视对象,也就是说这个同步 代码块同步在这个监视对象上,实例方法的同步就是使用该实例方法归属的对象作为监视对象的。

在同一个监视对象上,同步代码快内只能有一个线程执行。

接下来的例子里面,两个方法同时同步在它们被调用的实例上,因此他们同步在相同的对象上。

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 class MyClass {
    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }
    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);  
       }
    }
  }

在任意时刻只能有一个线程跑在两个同步方法中的一个内。

如果第二个同步代码块没有同步在MyClass.class 类对象上,那么在某一个时刻,每一个方法内就有可能 有一个线程在跑。

java同步的示例

下面这个例子中,表示如果有两个线程都调用counter的同一个实例上面的add方法,在某一个时刻在这个相同的实例上 只能有一个线程调用add方法,因为add方法是同步在是归属的实例上。

 public class Counter{
     long count = 0;
     public synchronized void add(long value){
       this.count += value;
     }
  }
public class CounterThread extends Thread{
     protected Counter counter = null;
     public CounterThread(Counter counter){
        this.counter = counter;
     }
     public void run() {
	for(int i=0; i<10; i++){
           counter.add(i);
        }
     }
  }
  public class Example {
    public static void main(String[] args){
      Counter counter = new Counter();
      Thread  threadA = new CounterThread(counter);
      Thread  threadB = new CounterThread(counter);
      threadA.start();
      threadB.start(); 
    }
  }

两个线程被创建以后,相同的一个counter被传入了线程的构造的方法。方法Counter.add()方法是同步时实例上面的, 因为add方法是实例的方法,并且被标记为了同步方法,因此,同一时间只能有一个线程访问add方法,另外的一个线程 会一直等待知道第一个线程离开add方法才能够执行add方法。

如果两个线程引用的是counter的不同的实例,那么它们同时调用add方法就没有什么问题,因为这个调用会大道不同的 实例上,然后方法会在不同的实例(各个方法归属的实例)上面进行同步,因此不会被阻塞。代码如下:

public class Example {
    public static void main(String[] args){
      Counter counterA = new Counter();
      Counter counterB = new Counter();
      Thread  threadA = new CounterThread(counterA);
      Thread  threadB = new CounterThread(counterB);
      threadA.start();
      threadB.start(); 
    }
  }

备注上面的代码中,两个线程A和B,不在针对同一个Counter实例,counterA和counterB上面的add方法同步在 各自归属的实例上,所以线程A调用counterA上面的add方法不会阻塞线程调用counterB上面的add方法。

并发编程工具

尽管synchronized关键字是java第一个关于访问多线程共享变量的方法,但是synchronized机制不是很先进。这也就是 java5提供了一个 concurrency 具集合的原因,这个工具集合能够来帮助开发者比synchronized更细粒度的控制并发



上一篇  Concurrence-12-Java's Volatile Keyword 下一篇   Concurrence-10-Java Memory Model