现在的位置: 首页 > 综合 > 正文

Effective Java 笔记(十二)

2013年05月24日 ⁄ 综合 ⁄ 共 3109字 ⁄ 字号 评论关闭

NO.48 对共享可变数据的同步访问 
  同步,不仅可以阻止一个线程看到对象处于不一致的状态中,它还可以保证通过一系列看似顺序执行的状态转变序列,对象从一种一致的状态变迁到另一种一致的状态。
  synchronized关键字可以保证在同一时刻,只有一个线程在执行一条语句,或者一段代码块。java语言保证读或写一个变量是原子的,除非这个变量的类型是long或double.
  java的内存模型决定,为了在线程之间可靠地通信,以及为了互斥访问,对原子数据的读写进行同步是需要的。看一个可怕的例子:

[java] view
plain
copy

  1. //Broken - require synchronization!  
  2. private static int nextSerialNumber=0;  
  3. public static int generateSerialNumber(){  
  4.    return nextSerialNumber++;  
  5. }  


  对其改进,只需要在generateSerialNumber的声明中增加synchronized修饰符即可。
  为了终止一个线程,一种推荐的做法是让线程轮询某个域,该域的值如果发生变化,就表明此线程就应该终止自己。下面的例子就是这个思路,但在同步出了问题。

[java] view
plain
copy

  1. //Broken - requires synchronization  
  2. public class StoppableThread extends Thread{  
  3. private boolean stopRequested=false;  
  4. public void run(){  
  5.   boolean done=false;  
  6.   while(!stopRequested && !done){  
  7.      ...//do what needs to be done in the thread  
  8.    }  
  9. }  
  10. public void requestStop(){  
  11.    stopRequested=true;  
  12.   }  
  13. }  


  对其改进如下:

[java] view
plain
copy

  1. //Properly synchronized cooperative thread temination  
  2. public class StoppableThread extends Thread{  
  3. private boolean stopRequested=false;  
  4. public void run(){  
  5.   boolean done=false;  
  6.   while(!stopRequested() && !done){  
  7.    ...//do what needs to be done in the thread  
  8.   }  
  9. }  
  10. public synchronized void requestStop(){  
  11.   stopRequested=true;  
  12. }  
  13. private synchronized boolean stopRequested(){  
  14.   return stopRequested;  
  15.  }  
  16. }  


  另一种改进是,将stopRequested声明为volatile,则同步可以省略。
  再来看迟缓初始化(lazy initialization)问题,双重访问模式并不一定都能正常工作,除非被共享的变量包含一个原语值。看例子:

[java] view
plain
copy

  1. //The double-check idion fro lazy initialization - broken!  
  2. private static Foo foo=null;  
  3. public static Foo getFoo(){  
  4.   if (foo==null){  
  5.     synchronized(Foo.class){  
  6.     if(foo==null)foo=new Foo();  
  7.     }  
  8.   }  
  9.   return foo;  
  10. }  


  最容易的修改是省去迟缓初始化:

[java] view
plain
copy

  1. //normal static initialization (not lazy)  
  2. private static finall Foo foo=new Foo();  
  3. public static Foo getFoo(){  
  4.    return foo;  
  5. }  


  或者使用正确的同步方法,但可能增加少许的同步开销:

[java] view
plain
copy

  1. //properly synchronized lazy initialization  
  2. private static Foo foo=null;  
  3. public static synchronized Foo getFoo(){  
  4.   if(foo==null)foo=new Foo();  
  5.   return foo;  
  6. }  


  按需初始化容器模式也不错,但是它只能用于静态域,不能用于实例域。

[java] view
plain
copy

  1. //The initialize-on-demand holder class idiom  
  2. private static class FooHolder(){  
  3.    static final Foo foo=new Foo();  
  4. }  
  5. public static Foo getFoo(){ return FooHolder.foo;}  


  简而言之,无论何时当多个线程共享可变数据的时候,每个读或写数据的线程必须获得一把锁。如果没有同步,则一个线程所做的修改就无法保证被另一个线程所观察到。


NO.51 不要依赖于线程调度器 
  不能让应用程序的正确性依赖于线程调度器。否则,结果得到的应用程序既不健壮也不具有可移植性。作为一个推论,不要依赖Thread.yield或者线程优先级。这些设施都只是影响到调度器,它们可以被用来提高一个已经能够正常工作的系统的服务质量,但永远不应用来“修正”一个原本并不能工作的程序。
  编写健壮的、响应良好的、可移植的多线程应用程序的最好办法是,尽可能确保在任何给定时刻只有少量的可运行线程。这种办法采用的主要技术是,让每个线程做少量的工作,然后使用Object.Wait等待某个条件发生,或者使用Thread.sleep睡眠一段时间。


NO.52 线程安全性的文档化 
  每个类都应该清楚地在文档中说明它的线程安全属性。在一个方法的声明中出现synchronized修饰符,这是一个实现细节,并不是导出的API文档的一部分。
  一个类为了可被多个线程安全地使用,必须在文档中清楚地说明它所支持的线程安全性级别。
    非可变性(immutable)-这个类的实例对于其它客户而言是不变的,不需要外部的同步。参见13条。
    线程程安全的(thread-safe)-这个类的实例是可变的,但是所有的地方都包含足够的同步手段,这些实例可以被并发使用无需外部同步。
    有条件的线程安全(conditionally thread-safe)-这个类(或关联的类)包含有某些方法,它们必须被顺序调用,而不能受到其它线程的干扰,除此之外,这种线程安全级别与上一种情形相同。为了消除被其他线程干扰的可能性,客户在执行此方法序列期间,必须获得一把适当的锁。如HashTable或Vector,它们的迭代器要求外部同步。如:

抱歉!评论已关闭.