java线程技术5_synchronized

来源:互联网 发布:我是皇进阶数据 编辑:程序博客网 时间:2024/05/02 01:53
1.概述
      从《java线程技术4_Volatile 》知道,Volatile只能解决多线程情况下共享变量的一致性问题,而解决不了共享数据操作的原子性问题。如下例
  1. /**
  2.  * 共享变量的并发错误问题.
  3.  * @version V1.0 ,2011-4-4 
  4.  * @author xiahui
  5.  */
  6. public class DutyDataThread extends Thread
  7. {
  8.     public static int n = 0;

  9.     public void run()
  10.     {
  11.         int m = n;
  12.         yield();
  13.         m++;
  14.         n = m;
  15.     }
  16.     public static void main(String[] args) throws Exception
  17.     {
  18.         DutyDataThread myThread = new DutyDataThread ();
  19.         Thread threads[] = new Thread[100];
  20.         for (int i = 0; i < threads.length; i++)
  21.             threads[i] = new Thread(myThread);
  22.         for (int i = 0; i < threads.length; i++)
  23.             threads[i].start();
  24.         for (int i = 0; i < threads.length; i++)
  25.             threads[i].join();
  26.         System.out.println("n = " + DutyDataThread.n);
  27.     }
  28. }
      这个程序启动了100个线程,然后每个线程将静态变量n加1.最后使用join方法使这100个线程都运行完后,再输出这个n值。按正常来讲,结果应该是n = 100.可偏偏结果小于100。由于run()并不是原子操作,存在脏数据问题,因此小于100。
       即使将public static int = 0;   改成   public  volanote static int n = 0;结果依然是小于100。

      synchronized关键字可以保证一个对象实例一个代码段只被一个线程调用。synchronized可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

      无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁。比如多个线程调用对象P1的同步方法,它们之间会形成互斥。但是对对象P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱。
     实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
      
2.synchronized与函数
格式:
      public synchronized void run(){ ... }

例如,将上面的代码改成如下
  1. public synchronized void run()
  2.     {
  3.         int m = n;
  4.         yield();
  5.         m++;
  6.         n = m;
  7.     }
则运行结果必为100。
      本程序只实例化一个对象实例myThread,创建了100个线程,调用myThread中的run(),由于加上了synchronized,run()同时只能被一个线程调用,并当前的run执行完后,才能被其他的线程调用。即使当前线程执行到了run方法中的yield方法,也只是暂停了一下。由于其他线程无法执行run方法,因此,最终还是会由当前的线程来继续执行。

注意:如果将代码改为如下
  1.         //DutyDataThread myThread = new DutyDataThread ();
  2.         Thread threads[] = new Thread[100];
  3.         for (int i = 0; i < threads.length; i++)
  4.             threads[i] = new Thread(new DutyDataThread ());
意味着创建了100个线程,每个线程实例化一个对象,这时synchronized就没有任何同步作用。其结果必小于100。

3.synchronized与静态方法

       对于静态方法来说,只要加上了synchronized关键字,这个方法就是同步的,无论是使用对象.method(),还是使用类.method()来调用method方法,method都是同步的,并不存在非静态方法的多个实例的问题。


4.synchronized和instance变量
      在23种设计模式中的单件(Singleton)模式如果按传统的方法设计,也是线程不安全的,下面的代码是一个线程不安全的单件模式。代码如下
  1. /**
  2.  * 单例类.
  3.  * @version V1.0 ,2011-4-4 
  4.  * @author xiahui
  5.  */
  6. public class Singleton {
  7.     private static Singleton sample;

  8.     private Singleton(){
  9.     }
  10.     public static Singleton getInstance(){
  11.         if (sample == null){
  12.             Thread.yield(); // 为了放大Singleton模式的线程不安全性
  13.             sample = new Singleton();
  14.         }
  15.         return sample;
  16.     }
  17. }
测试是否只创建了一个对象
  1. /**
  2.  * 单例线程类.
  3.  * @version V1.0 ,2011-4-4 
  4.  * @author xiahui
  5.  */
  6. public class SingletonThread extends Thread
  7. {
  8.     public void run(){
  9.         Singleton singleton = Singleton.getInstance();
  10.         System.out.println(singleton.hashCode());
  11.     }
  12.     public static void main(String[] args)
  13.     {
  14.         Thread threads[] = new Thread[5];
  15.         for (int i = 0; i < threads.length; i++)
  16.             threads[i] = new SingletonThread();
  17.         for (int i = 0; i < threads.length; i++)
  18.             threads[i].start();
  19.     }
  20. }
程序运行结果如下
  1. 27744459
  2. 24355087
  3. 27744459
  4. 6927154
  5. 28737396
      运行结果可能在不同的运行环境上有所有同,但一般这五行输出不会完全相同。从这个输出结果可以看出,通过getInstance方法得到的对象实例是五个,而不是我们期望的一个。这是因为当一个线程执行了Thread.yield()后,就将CPU资源交给了另外一个线程。由于在线程之间切换时并未执行到创建Singleton对象实例的语句,因此,这几个线程都通过了if判断,所以,就会产生了建立五个对象实例的情况。

解决方法1:为getInstance加上synchronized关键字
      public static synchronized Singleton getInstance() {   }

解决方法2:在定义Singleton变量时就建立Singleton对象
      private static final Singleton sample = new Singleton(); 


5.注意事项
5.1synchronized不能继承
      如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。
      当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
  1. public class Child extends Parent
  2. {
  3.     public  synchronized void method() { super.method(); }//二者任选其一
  4. }

5.2 在定义接口方法时不能使用synchronized关键字

5.3 构造方法不能使用synchronized关键字

5.4 synchronized可以自由放置
正确代码
  1. public synchronized void method();
  2. synchronized public void method();
  3. public static synchronized void method();
  4. public synchronized static void method();
  5. synchronized public static void method();
synchronized不能放在方法返回类型的后面,如下面的代码是错误的:
  1. public void synchronized method();
  2. public static void synchronized method();
synchronized关键字只能用来同步方法,不能用来同步类变量,如下面的代码也是错误的:
  1. public synchronized int n = 0;
  2. public static synchronized int n = 0;

5.5使用synchronized对整个对象的方法调用都有影响
      synchronized锁定的是一个类。
      如果在非静态方法method1和method2定义时都使用了synchronized,在 method1未执行完之前,method2是不能执行的。
      静态方法和非静态方法的情况类似。但静态和非静态方法不会互相影响。
  1. /**
  2.  * 当一个非静态函数,其它非静态函数无法执行. 
  3.  * 当一个静态函数,其它静态函数无法执行.
  4.  * @version V1.0 ,2011-4-4
  5.  * @author xiahui
  6.  */
  7. public class synchronizedThread extends Thread {
  8.     public String methodName;

  9.     public static void method(String s) {
  10.         System.out.println(s);
  11.         while (true)// 死循环
  12.             ;
  13.     }
  14.     public synchronized void method1() {
  15.         method("非静态的method1方法");
  16.     }
  17.     public synchronized void method2() {
  18.         method("非静态的method2方法");
  19.     }
  20.     public static synchronized void method3() {
  21.         method("静态的method3方法");
  22.     }
  23.     public static synchronized void method4() {
  24.         method("静态的method4方法");
  25.     }
  26.     public void run() {
  27.         try {
  28.             getClass().getMethod(methodName).invoke(this);
  29.         } catch (Exception e) {
  30.         }
  31.     }
  32.     public static void main(String[] args) throws Exception {
  33.         synchronizedThread myThread1 = new synchronizedThread();
  34.         for (int i = 1; i <= 4; i++) {
  35.             myThread1.methodName = "method" + String.valueOf(i);
  36.             new Thread(myThread1).start();
  37.             sleep(100);
  38.         }
  39.     }
  40. }
运行结果如下:
  1. 非静态的method1方法
  2. 静态的method3方法
非静态方法method1运行时,method2不能运行
静态方法method3运行时,method4不能运行

6.synchronized与代码块
6.1synchronized与非静态函数代码块
  1. public class SyncBlock {
  2.     public void method1()
  3.     {
  4.        synchronized(this) // 相当于对method1方法使用synchronized关键字
  5.        {
  6.             … …
  7.         }
  8.     }

  9.    public void method2()
  10.    {
  11.         synchronized(this) // 相当于对method2方法使用synchronized关键字
  12.        {
  13.            … …
  14.         }
  15.    }
  16.    public synchronized void method3()
  17.    {
  18.        
  19.    }
  20. }
使用同一个SyncBlock类实例时,这三个方法只要有一个正在执行,其他两个方法就会因未获得同步锁而被阻塞。

6.2synchronized与内部类非静态函数代码块
      在内类(InnerClass)的方法中使用synchronized块来时,this只表示内类,和外类(OuterClass)没有关系。但内类的非静态方法可以和外类的非静态方法同步
  1. public class SyncBlock
  2. {
  3.     … …
  4.     class InnerClass
  5.     {
  6.         public void method4()
  7.         {
  8.             synchronized(SyncBlock.this)
  9.             {
  10.                 … … 
  11.             }
  12.         }
  13.     }
  14.     … …
  15. }
InnerClass类的method4方法和SyncBlock类的其他三个方法同步,因此,method1、method2、method3和method4四个方法在同一时间只能有一个方法执行。

6.3synchronized与静态函数代码块
     在调用静态方法时,对象实例不一定被创建。因此,就不能使用this来同步静态方法,而必须使用Class对象来同步静态方法。
  1. public class StaticSyncBlock{
  2.        public static void method1(){
  3.            synchronized(StaticSyncBlock.class) {
  4.                … …
  5.            }
  6.        }
  7.        public static synchronized void method2() {
  8.            … …
  9.        }
  10. }
method1和method2方法同时只能有一个方法执行。除了使用class字段得到Class对象外,还可以使用实例的getClass方法来得到Class对象。
  1. public class StaticSyncBlock{
  2.     public static StaticSyncBlock instance; 
  3.     public StaticSyncBlock(){
  4.         instance = this;
  5.     }
  6.     public static void method1(){
  7.        synchronized(instance.getClass()){
  8.             
  9.        }
  10.     }
  11. }

      我们可以通过Class对象使不同类的静态方法同步,如Test类的静态方法method和StaticSyncBlock类的两个静态方法同步。
  1. public class Test
  2. {
  3.        public static void method(){
  4.            synchronized(StaticSyncBlock.class){
  5.            }
  6.        }
  7. }
Test类的method方法和StaticSyncBlock类的method1、method2方法同步。

注意:
        在使用synchronized块同步类方法时,非静态方法可以使用this来同步,而静态方法必须使用Class对象来同步。它们互不影响。当 然,也可以在非静态方法中使用Class对象来同步静态方法。但在静态方法中不能使用this来同步非静态方法。这一点在使用synchronized块 同步类方法时应注意。
原创粉丝点击