顯示具有 Design Pattern(Creational) 標籤的文章。 顯示所有文章
顯示具有 Design Pattern(Creational) 標籤的文章。 顯示所有文章

2011年5月19日 星期四

Java - Singletone Pattern &Double-checked locking

今天看到一篇文章在比較ConcurrentHashMap and HashMap with synchronized 的效能, 另一篇文章又在說明ConcurrentHashMap 的內容實作,ConcurrentHashMap 裡面使用了一些技巧(JMM) , 看到JMM又跑去看了一下,後來又看到了Double-checked locking, 以往實作Singleton時不會考慮到的一些問題。

wiki裡面的例子, 基本的singleton 實作, 在單thread下
class Foo {
    private Helper helper = null;
    public synchronized Helper getHelper() {
        if (helper == null)
            helper = new Helper();
        return helper;
    }
 
    // other functions and members...
}

但是這個在multi-thread下是不行的, 所以會有一種解決方式:
// Correct multithreaded version
class Foo { 
  private Helper helper = null;
  public synchronized Helper getHelper() {
    if (helper == null) 
        helper = new Helper();
    return helper;
    }
  // other functions and members...
  }

但是這種寫法效率上又不是很好,所以會有另一種的改進方法
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}

但是文章中又提到,上面這種看起來是work的,但是實際上是行不通的,因為new Helper()在很多平台和優化編譯器上是錯誤的。原因在於:helper = new Helper()這行代碼在不同編譯器上的行為是無法預知的。

看看另一個例子,
public  class  LazySingleton {  
    private  int  someField;  
      
    private  static  LazySingleton instance;  
      
    private  LazySingleton() {  
        this .someField = new  Random().nextInt( 200 )  1 ;      
    }  
      
    public  static  LazySingleton getInstance() {  
        if  (instance == null ) {                               
            synchronized (LazySingleton. class ) {           
                if  (instance == null ) {                    
                    instance = new  LazySingleton();        
                }  
            }  
        }  
        return  instance;                                  
    }  
      
    public  int  getSomeField() {  
        return  this .someField;                             
    }  
}  
這個例子下, 儘管得到了LazySingleton的正確的引用,但是卻有可能訪問到其成員變量的不正確值,具體來說LazySingleton.getInstance().getSomeField()有可能返回someField的默認值0。如果程序行為正確的話,這應當是不可能發生的事,因為在構造函數里設置的someField的值不可能為0。也說明這種情況理論上有可能發生。
DCL的分析也告訴我們一條經驗原則, 對引用(包括對象引用和數組引用)的非同步訪問,即使得到該引用的最新值,卻並不能保證也能得到其成員變量(對數組而言就是每個數組元素)的最新值。LazySingleton是一個不變class,它只有get方法而沒有set方法,即使對於不可變對象,它也必須被安全的發布,才能被安全地共享。 所謂“安全的共享”就是說不需要同步也不會遇到數據競爭的問題。 在Java5或以後,將someField聲明成final的,即使它不被安全的發布,也能被安全地共享,而在Java1.4或以前則必須被安全地發布。

這種設計上的問題要如何解決?
在JDK 1.5裡面,我們可以這樣做

// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class Foo {
    private volatile Helper helper = null;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null)
                    helper = result = new Helper();
            }
        }
        return result;
    }
 
    // other functions and members...
}
將object 設為volatile , 注意volatile只能保證多線程的內存可見性,不能保證多線程的執行有序性。
以下的a=a+count包含很多動作, 多個線程的執行是無序的,因為沒有任何機制來保證多個線程的執行有序性和原子性。 volatile存在的意義是,任何線程對a的修改,都會馬上被其他線程讀取到

public  class  VolatileTest{  
      public  volatile  int  a;  
      public  void  add( int  count){  
           a=a+count;  
           //a++;
      }  
    }  
在effective中提到的,簡單實現的方法
public  class  Singleton {  
      
      private  Singleton() {}  
      
      // Lazy initialization holder class idiom for static fields  
      private  static  class  InstanceHolder {  
       private  static  final  Singleton instance = new  Singleton();  
      }  
      
      public  static  Singleton getSingleton() {   
        return  InstanceHolder.instance;   
      }  
    }  

或著實作threadlocal
class Foo {
  /** If perThreadInstance.get() returns a non-null value, this thread
  has done synchronization needed to see initialization
  of helper */
         private final ThreadLocal perThreadInstance = new ThreadLocal();
         private Helper helper = null;
         public Helper getHelper() {
             if (perThreadInstance.get() == null) createHelper();
             return helper;
         }
         private final void createHelper() {
             synchronized(this) {
                 if (helper == null)
                     helper = new Helper();
             }
      // Any non-null value would do as the argument here
             perThreadInstance.set(perThreadInstance);
         }
 }

wiki 上的另一個例子,設成靜態的
// Correct lazy initialization in Java 
@ThreadSafe
class Foo {
    private static class HelperHolder {
       public static Helper helper = new Helper();
    }
    public static Helper getHelper() {
        return HelperHolder.helper;
    }
}

一樣是使用volatile 跟 final , (JDK 1.5 對volatile & final 有加強一些語意,針對JMM)
public class FinalWrapper<T>
{
    public final T value;
    public FinalWrapper(T value) { this.value = value; }
}
 
public class Foo
{
   private FinalWrapper<Helper> helperWrapper = null;
   public Helper getHelper()
   {
      FinalWrapper<Helper> wrapper = helperWrapper;
      if (wrapper == null)
      {
          synchronized(this)
          {
              if (helperWrapper ==null)
                  helperWrapper = new FinalWrapper<Helper>( new Helper() );
              wrapper = helperWrapper;
          }
      }
      return wrapper.value;
   }
}



More infomation :
Performance comparision between ConcurrentHashMap and synchronized HashMap in Terracotta

ConcurrentHashMap

Wikipedia : Double-checked locking

The "Double-Checked Locking is Broken" Declaration

Jeremy Manson Blog:Double Checked Locking

用happen-before規則重新審視DCL

2011年5月17日 星期二

Design Pattern - Builder Pattern

Builder pattern, 可以將一個物件內部的"表象"跟物件產生的過程分開來,在建造的過程中產生不同的物件,
簡單的說,如果要把建立複雜組件與運用組件方式分離,就可以使用Builder Pattern.

Builder pattern 的Class diagram :
From wiki:
Builder
Abstract interface for creating objects (product).
Concrete Builder
Provides implementation for Builder. It is an object able to construct other objects. Constructs and assembles parts to build the objects.
Director
The Director class is responsible for managing the correct sequence of object creation. It receives a Concrete Builder as a parameter and executes the necessary operations on it.
Product
The final object that will be created by the Director using Builder.


wiki的例子是用Pizza , Pizza 就是一個Product , 會有一個抽像的PizzaBuilder 用來建立不同的pizza部份, 然後實作HawaiianPizzaBuilder and SpicyPizzaBuilder (ConcreteBuilder),再透過Director - Cook去把完整的pizza 實作出來。

如果我有一個Product , 每個Product 裡面的零件都不一樣, 也能套上Builder Pattern,首先建立一個Product 類別如下:
/** "Product" */
package builder;

import java.util.ArrayList;
import java.util.List;

public class Product {
 List<String> parts = new ArrayList<String>();
 
 public void addPart(String part){
  parts.add(part);
 }
 
 public void showProd(){
  for (int i = 0; i < parts.size(); i  ) {
   System.out.println("Prod infor =  "  parts.get(i));
  }
 }
 
}


再來建立一個Abstract Builder , 用來建立每個Product 的零件
/** "Abstract Builder" */
package builder;

public abstract class PartBuilder {
 protected Product prod;
 
 public Product getProduct(){
  return this.prod;
 }
 
 public void createNewProd(){
  this.prod = new Product();
 }
 
 public abstract void buildeAddPart();
}


然後再建立一個Concrete Builder
/** "ConcreteBuilder" */
package builder;

public class ProdABuilder extends PartBuilder {

 @Override
 public void buildeAddPart() {
  prod.addPart("PART1");  
 }

}

然後是Director的部份,負責把產品建立出來
/** "Director" */
package builder;

public class Director {
 private PartBuilder partbuilder;
 public void setPartbuilder(PartBuilder p){
  this.partbuilder = p;
 }
 
 public Product getProd(){
  return partbuilder.getProduct();
 }
 
 public void createProduct(){
  partbuilder.createNewProd();
  partbuilder.buildeAddPart();
 }
 
}

最後就能使用
package builder;

public class builderpattern {

 /**
  * @param args
  */
 public static void main(String[] args) {
  Director director = new Director();
  PartBuilder prodaBuilder = new ProdABuilder();
    
  director.setPartbuilder(prodaBuilder);
  director.createProduct();
    
  Product prod = director.getProd();
  prod.showProd();

 }

}




在Java Bean 裡面, 一般的Bean 長成這樣,
package builder;

public class UserBeanBuilder {
 private long userId;
 private String userName;
 private String userDesc;
 
 public long getUserId() {
  return userId;
 }
 
 public void setUserId(long userId) {
  this.userId = userId;
 }
 public String getUserName() {
  return userName;
 }
 public void setUserName(String userName) {
  this.userName = userName;
 }
 public String getUserDesc() {
  return userDesc;
 }
 public void setUserDesc(String userDesc) {
  this.userDesc = userDesc;
 }
 
 
 
 

}


但是如果有很多屬性,而我們又必需讓bean建立時有一些初始值, Constructor 就會寫很多個, Builder Pattern 在Effective Java也有提到
Item2 Consider using a Builder when faced with many constructor parameters


經過修改之後Bean 會長這樣
package builder;

public class UserBeanBuilder {
 private long userId;
 private String userName;
 private String userDesc;
 
 public long getUserId() {
  return userId;
 }

 public String getUserName() {
  return userName;
 }

 public String getUserDesc() {
  return userDesc;
 }

 public static class Builder{
  private long userId;
  private String userName;
  private String userDesc;
  
  public Builder(long userId,String userName){
   this.userId = userId;
   this.userName = userName;
  }
  
  public Builder userDesc(String desc) {
   this.userDesc = desc;
   return this;
  }
  
  public UserBeanBuilder build(){
   return new UserBeanBuilder(this);
  }
 }
 
 private UserBeanBuilder(Builder builder){
  this.userId = builder.userId;
  this.userName = builder.userName;
  this.userDesc = builder.userDesc;
 }
}


調用
package builder;

public class UserCreator {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  UserBeanBuilder user = new UserBeanBuilder.Builder(100, "MOMO").userDesc("CHECK THIS").build();
  
  System.out.println(user.getUserId());
  System.out.println(user.getUserName());
  System.out.println(user.getUserDesc());
  
 }

}



利用在一個 class 中內置的 public static class Builder ,並將原先的 constructor 封裝在該 class 中,我們可以免除以下問題:
1:Ugly constructor. 例如:new NutritionFacts(int servingSize, int servings,int calories, int fat, int 15 more optional params!);
2:Constructor telescoping. 如果是上例的 constructor ,我們在使用的時候一定得參看原先的Cnstructor 說明,才能知道各個參數的意義。如:NutritionFacts locoCola =new NutritionFacts(240, 8, 0, 0, 30, 28); ,無法了解這串數字是什麼
3:Too many setters required when creating objects.


Reference :
Wikipedia - Builder pattern

Book Review: Effective Java 2nd Edition