分享

java并发(八)Java同步块synchronized

 roydocs 2015-04-15
    笔者在刚开始使用synchronized的时候,对并发的疑惑很多.因此在这里总结一下,与大家分享.关键是"等",而不是"舍弃"线程。而且“同步”这个术语除了synchronized意外,还包括volatile、显示锁、原子变量。

Java中的每一个对象都可以作为锁。

对于同步实例方法,锁是当前实例对象。

对于同步静态方法,锁是当前对象的Class对象。

对于同步方法块,锁是Synchonized括号里配置的对象。



锁提供了两种主要特性

    互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。



1.锁方法

静态方法

根据类进行锁,同一个类的静态方法,锁是互斥的。子类和父类的静态方法不互斥。



对象方法

对象方法相当于synchronized(this),只是根据对象来进行锁。同一个对象中的synchronized是互斥的。



2.锁代码块

根据对象进行锁,无论代码在什么位置。只要是同一个锁对象,则互斥。



详细介绍

Java 同步块(synchronized block)用来标记方法或者代码块是同步的。Java同步块用来避免竞争。

Java 同步关键字(synchronized)

Java中的同步块用synchronized标记。同步块在Java中是同步在某个对象上。所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作。所有其他等待进入该同步块的线程将被阻塞,直到执行该同步块中的线程退出。

有四种不同的同步块:

实例方法

静态方法

实例方法中的同步块

静态方法中的同步块

上述同步块都同步在不同对象上。实际需要那种同步块视具体情况而定。



实例方法同步

下面是一个同步的实例方法:

Java代码 复制代码 收藏代码
  1. public synchronized void add(int value){  
  2. this.count += value;  
  3. }  


注意在方法声明中同步(synchronized )关键字。这告诉Java该方法是同步的。

Java实例方法同步是同步在拥有该方法的对象上。这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。只有一个线程能够在实例方法同步块中运行。如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。一个实例一个线程。



静态方法同步

静态方法同步和实例方法同步方法一样,也使用synchronized 关键字。Java静态方法同步如下示例:

Java代码 复制代码 收藏代码
  1. public static synchronized void add(int value){  
  2.  count += value;  
  3. }  


同样,这里synchronized 关键字告诉Java这个方法是同步的。

静态方法的同步是指同步在该方法所在的类对象上。因为在Java虚拟机中一个类只能对应一个类对象,所以同时只允许一个线程执行同一个类中的静态同步方法。

对于不同类中的静态同步方法,一个线程可以执行每个类中的静态同步方法而无需等待。不管类中的那个静态同步方法被调用,一个类只能由一个线程同时执行。



实例方法中的同步块

有时你不需要同步整个方法,而是同步方法中的一部分。Java可以对方法的一部分进行同步。

在非同步的Java方法中的同步块的例子如下所示:

Java代码 复制代码 收藏代码
  1. public void add(int value){  
  2.     synchronized(this){  
  3.        this.count += value;  
  4.     }  
  5. }  


示例使用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实例对象上,那么两个方法可以被线程同时执行。



静态方法中的同步块

和上面类似,下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。

Java代码 复制代码 收藏代码
  1. public class MyClass {  
  2.     public static synchronized void log1(String msg1, String msg2){  
  3.        log.writeln(msg1);  
  4.        log.writeln(msg2);  
  5.     }  
  6.     public static void log2(String msg1, String msg2){  
  7.        synchronized(MyClass.class){  
  8.           log.writeln(msg1);  
  9.           log.writeln(msg2);  
  10.        }  
  11.     }  
  12. }  


这两个方法不允许同时被线程访问。

如果第二个同步块不是同步在MyClass.class这个对象上。那么这两个方法可以同时被线程访问。



Java同步实例

在下面例子中,启动了两个线程,都调用Counter类同一个实例的add方法。因为同步在该方法所属的实例上,所以同时只能有一个线程访问该方法。

Java代码 复制代码 收藏代码
  1. public class Counter{  
  2.      long count = 0;  
  3.      public synchronized void add(long value){  
  4.        this.count += value;  
  5.      }  
  6.   }  
  7.   public class CounterThread extends Thread{  
  8.      protected Counter counter = null;  
  9.      public CounterThread(Counter counter){  
  10.         this.counter = counter;  
  11.      }  
  12.      public void run() {  
  13.     for(int i=0; i<10; i++){  
  14.            counter.add(i);  
  15.         }  
  16.      }  
  17.   }  
  18.   
  19.   public class Example {  
  20.     public static void main(String[] args){  
  21.       Counter counter = new Counter();  
  22.       Thread  threadA = new CounterThread(counter);  
  23.       Thread  threadB = new CounterThread(counter);  
  24.       threadA.start();  
  25.       threadB.start();  
  26.     }  
  27. }  


创建了两个线程。他们的构造器引用同一个Counter实例。Counter.add方法是同步在实例上,是因为add方法是实例方法并且被标记上synchronized关键字。因此每次只允许一个线程调用该方法。另外一个线程必须要等到第一个线程退出add()方法时,才能继续执行方法。



如果两个线程引用了两个不同的Counter实例,那么他们可以同时调用add()方法。这些方法调用了不同的对象,因此这些方法也就同步在不同的对象上。这些方法调用将不会被阻塞。如下面这个例子所示:

Java代码 复制代码 收藏代码
  1. public class Example {  
  2.    public static void main(String[] args){  
  3.      Counter counterA = new Counter();  
  4.      Counter counterB = new Counter();  
  5.      Thread  threadA = new CounterThread(counterA);  
  6.      Thread  threadB = new CounterThread(counterB);  
  7.      threadA.start();  
  8.      threadB.start();  
  9.    }  
  10. }  


注意这两个线程,threadA和threadB,不再引用同一个counter实例。CounterA和counterB的add方法同步在他们所属的对象上。调用counterA的add方法将不会阻塞调用counterB的add方法
 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多