我思故我强

Java线程之同步化(Synchronized)

1.  如果一个对象所持有的数据可以被多线程同时共享存取,必须考虑到数据同步的问题。所谓数据同步指的是两份数据的整体性和一致性。数据在多线程下共享时容易由于同时多个线程可能更新同一个对象的信息,而造成对象数据的不同步,因为数据的不同步可能引发的错误通常不易察觉,而且可能是在程序执行了几千几万次之后,才会发生错误。这通常会发生在产品已经上线之后,甚至是程序已经执行了几年之后。

2. 举个简单的例子,设计了一个PersonalInfo类:   

Java代码 
  1. package  ysu.hxy;   
  2.   
  3. public   class  PersonalInfo    
  4. {   
  5.      private  String name;   
  6.      private  String id;   
  7.      private   int  count;   
  8.   
  9.      public  PersonalInfo()   
  10.     {   
  11.         name =  "nobody" ;   
  12.         id =  "N/A" ;   
  13.     }   
  14.   
  15.      public   void  setNameAndID(String name,String id)   
  16.     {   
  17.          this .name = name;   
  18.          this .id = id;   
  19.          if (!checkNameAndIDEqual())   
  20.         {   
  21.              System.out.println(count +  ") illegal name or ID..." );   
  22.         }   
  23.         count ++;   
  24.     }   
  25.   
  26.      private   boolean  checkNameAndIDEqual(){   
  27.          return  (name.charAt( 0 ) == id.charAt( 0 )) ?  true : false ;   
  28.     }   
  29. }  

 单就这个类本身而言,它并没有任何的错误,但如果它被用于多线程的程序中,而且同一个对象被多个线程存取时,就会有可能发生错误。下面是一个简单的测试程序,看看PersonalInfo类在多线程共享数据下会发生什么问题。

Java代码 
  1. package  ysu.hxy;   
  2.   
  3. public   class  PersonalInfoTest    
  4. {   
  5.      public   static   void  main(String[] args)    
  6.     {   
  7.          final  PersonalInfo person =  new  PersonalInfo();   
  8.   
  9.          //假设会能两个线程可能更新person对象    
  10.         Thread thread1 =  new  Thread( new  Runnable() {   
  11.              public   void  run() {   
  12.                  while ( true ){   
  13.                     person.setNameAndID( "Justin Lin" , "J.L" );   
  14.                 }   
  15.             }   
  16.         });   
  17.   
  18.         Thread thread2 =  new  Thread( new  Runnable() {   
  19.              public   void  run() {   
  20.                  while ( true ){   
  21.                     person.setNameAndID( "Shang Hwang Lin" , "S.H" );   
  22.                 }   
  23.             }   
  24.         });   
  25.   
  26.         System.out.println();   
  27.   
  28.         thread1.start();   
  29.         thread2.start();   
  30.     }   
  31. }  

 

执行结果:

D:\hxy>java ysu.hxy.PersonalInfoTest
开始测试...
23466451) illegal name or ID...
78044494) illegal name or ID...
101630476) illegal name or ID...
106496643) illegal name or ID...
145330181) illegal name or ID...
169674022) illegal name or ID...
174072203) illegal name or ID...
214717201) illegal name or ID...
219668799) illegal name or ID...
240921750) illegal name or ID...
265875722) illegal name or ID...
270920923) illegal name or ID...
281256783) illegal name or ID...

这个程序出现了错误,在23466451次的setNameAndID()执行时就开始了。如果程序完成并开始应用于实际场合之后,这个时间点可能是几个月甚至是几年之后。问题出在这里:

Java代码 
  1. public   void  setNameAndID(String name,String id)   
  2.     {   
  3.          this .name = name;   
  4.          this .id = id;   
  5.          if (!checkNameAndIDEqual())   
  6.         {   
  7.              System.out.println(count +  ") illegal name or ID..." );   
  8.         }   
  9.         count ++;   
  10.     }  

      虽然传递给setNameAndID()的变量并没有问题,在某个时间点时,thread1设定了Justin Lin、J.L给name和id,在进行if测试的前一刻,thread2可能此时刚好调用setNameAndID("Shang Hwang","S.H")。在name被设定为Shang HWang时,checkNameAndIDEqual()开始执行,此时name等于Shang HWang,而id还是J.L。所以,checkNameAndIDEqual()就会返回false,结果就显示了错误信息。

       必须同步数据对对象的更新,方法在有一个线程正在设定person对象的数据时,不可以被另一个线程同时进行设定。可以使用synchronized关键词来进行这个动作。

Java代码 
  1. public   synchronized   void  setNameAndID(String name,String id)   
  2.     {   
  3.          this .name = name;   
  4.          this .id = id;   
  5.          if (!checkNameAndIDEqual())   
  6.         {   
  7.              System.out.println(count +  ") illegal name or ID..." );   
  8.         }   
  9.         count ++;   
  10.     }  

 这是synchronized关键词的一个使用方式,用于方法上让方法的范围内都成为被同步化区域。被同步化区域在有一个线程占据时就像一个禁区,不允许其他线程进入。由于同时间只能有一个线程在被同步化区域,所以更新共享数据时,就像单线程程序在更新数据一样,以保证对象中的数据会与给定的数据同步。

  sychronized的设定不只可用于方法上,也可以用于限定某个程序区块上被同步化区域。例如: 

Java代码 
  1. public   void  setNameAndID(String name,String id)   
  2.   {  //同步某个程序区块   
  3.         synchronized ( this )   
  4.    {   
  5.         this .name = name;   
  6.         this .id = id;   
  7.         if (!checkNameAndIDEqual())   
  8.           {   
  9.                System.out.println(count+ ") illegal name or ID..." );   
  10.           }   
  11.    }   
  12. }  

   这个程序片段的意思是,在线程执行到synchronized设定的被同步化区块时锁定当前对象,这样就没有其他线程可以来执行这个被同步化区块。这个方式可以应用于您不想锁定整个方法区块,而只是想在更新共享数据时再确保对象与数据的同步化。由于只锁定方法中的某个区块,在执行完区块后即释放对对象的锁定,以便让其他线程能有机会对对象进行操作,相对于锁定整个方法区块效率较高。

   也可以标示某个对象要求同步化。例如在多线程中存取同一个ArrayList对象时,由于ArrayList并没有实现数据存取时的同步化,所以当它使用多线程环境时,必须注意多个线程存取同一个ArrayList时,有可能发生两个以上的线程将数据存入ArrayList的同一个位置,造成数据的相互覆盖。为了确保数据存入时的正确性,可以在存取ArrayList对象时要求同步化。例如:

Java代码 
  1. //arraylist参考至一个ArrayList的一个实例   
  2. synchronized (arraylist)   
  3. {   
  4.      arrayList.add( new  SomeClass());   
  5. }  

 同步化确保数据的同步,但所牺牲的就是在于一个线程占据同步化区块,而其他线程等待它释放区块执行权时的延迟。这在线程少时可能看不出来,但在线程多的环境中必然造成一定的效率问题(例如大型网站的多人联机时)。

posted on 2009-10-14 11:52 李云泽 阅读(437) 评论(0)  编辑  收藏 所属分类: 面试笔试相关的


只有注册用户登录后才能发表评论。


网站导航: