熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java高級技術 >> 正文

Java中的模式--單態

2022-06-13   來源: Java高級技術 

  單態定義:

  Singleton模式主要作用是保證在Java應用程序中一個類Class只有一個實例存在

  Singleton模式就為我們提供了這樣實現的可能使用Singleton的好處還在於可以節省內存因為它限制了實例的個數有利於Java垃圾回收(garbage collection)

  使用Singleton注意事項

  有時在某些情況下使用Singleton並不能達到Singleton的目的如有多個Singleton對象同時被不同的類裝入器裝載在EJB這樣的分布式系統中使用也要注意這種情況因為EJB是跨服務器跨JVM的

  單態模式的演化

  單態模式是個簡單的模式但是這個簡單的模式也有很多復雜的東西

  (注意在這裡補充一下現在單態模式其實有一個寫法是不錯的見這裡但還是建議看完這篇文章因為解釋的事情是不一樣的這裡說的是為什麼doublechecked不能使用

  一首先最簡單的單態模式單態模式

  import javautil*;

  class Singleton

  {

  private static Singleton instance;

  private Vector v;

  private boolean inUse;

  private Singleton()

  {

  v = new Vector();

  vaddElement(new Object());

  inUse = true;

  }

  public static Singleton getInstance()

  {

  if (instance == null)          //

  instance = new Singleton();  //

  return instance;               //

  }

  }

  這個單態模式是不安全的為什麼說呢 ?因為沒考慮多線程如下情況

  Thread 調用getInstance() 方法並且判斷instance是null然後進入if模塊

  在實例化instance之前

  Thread 搶占了Thread 的cpu

  Thread 調用getInstance() 方法並且判斷instance是null然後進入if模塊

  Thread 實例化instance 完成返回

  Thread 再次實例化instance

  這個單態已經不在是單態

  二為了解決剛才的問題單態模式

  public static synchronized Singleton getInstance()

  {

  if (instance == null)          //

  instance = new Singleton();  //

  return instance;               //

  }

  采用同步來解決這種方式解決了問題但是仔細分析正常的情況下只有第一次時候進入對象的實例化須要同步其它時候都是直接返回已經實例化好的instance不須要同步大家都知到在一個多線程的程序中如果同步的消耗是很大的很容易造成瓶頸

  三為了解決上邊的問題單態模式加入同步

  public static Singleton getInstance()

  {

  if (instance == null)

  {

  synchronized(Singletonclass) {

  instance = new Singleton();

  }

  }

  return instance;

  }

  同步改成塊同步而不使用函數同步但是仔細分析

  又回到了模式一的狀態再多線程的時候根本沒有解決問題

  四為了對應上邊的問題單態模式也就是很多人采用的Doublechecked locking

  public static Singleton getInstance()

  {

  if (instance == null)

  {

  synchronized(Singletonclass) {  //

  if (instance == null)          //

  instance = new Singleton();  //

  }

  }

  return instance;

  }

  這樣模式一中提到的問題解決了不會出現多次實例化的現象

  當第一次進入的時候保正實例化時候的單態在實例化後多線程訪問的時候直接返回不須要進入同步模塊既實現了單態又沒有損失性能表面上看我們的問題解決了但是再仔細分析

  我們來假象這中情況

  Thread :進入到//位置執行new Singleton()但是在構造函數剛剛開始的時候被Thread搶占cpu

  Thread :進入getInstance()判斷instance不等於null返回instance

  (instance已經被new已經分配了內存空間但是沒有初始化數據)

  Thread :利用返回的instance做某些操做失敗或者異常

  Thread :取得cpu初始化完成

  過程中可能有多個線程取到了沒有完成的實例並用這個實例作出某些操做

  -----------------------------------------

  出現以上的問題是因為

  mem = allocate();             //分配內存

  instance = mem;               //標記instance非空

  //未執行構造函數thread 從這裡進入

  ctorSingleton(instance);      //執行構造函數

  //返回instance

  ------------------------------------------

  五證明上邊的假想是可能發生的字節碼是用來分析問題的最好的工具可以利用它來分析下邊一段程序(為了分析方便所以漸少了內容)

  字節碼的使用方法見這裡利用字節碼分析問題

  class Singleton

  {

  private static Singleton instance;

  private boolean inUse;

  private int val;

  private Singleton()

  {

  inUse = true;

  val = ;

  }

  public static Singleton getInstance()

  {

  if (instance == null)

  instance = new Singleton();

  return instance;

  }

  }

  得到的字節碼

  ;asm code generated for getInstance

  DB   mov         eax[C]      ;load instance ref

  DB   test        eaxeax             ;test for null

  DB   jne         DD

  DB   mov         eaxCh

  DBE   call        EFF            ;allocate memory

  DC   mov         [C]eax      ;store pointer in

  ;instance ref instance

  ;nonnull and ctor

  ;has not run

  DC   mov         ecxdword ptr [eax]

  DCA   mov         dword ptr [ecx]   ;inline ctor inUse=true;

  DD   mov         dword ptr [ecx+] ;inline ctor val=;

  DD   mov         ebxdword ptr ds:[Ch]

  DDD   jmp         DB

  上邊的字節碼證明猜想是有可能實現的

  六好了上邊證明Doublechecked locking可能出現取出錯誤數據的情況那麼我們還是可以解決的

  public static Singleton getInstance()

  {

  if (instance == null)

  {

  synchronized(Singletonclass) {      //

  Singleton inst = instance;         //

  if (inst == null)

  {

  synchronized(Singletonclass) {  //

  inst = new Singleton();        //

  }

  instance = inst;                 //

  }

  }

  }

  return instance;

  }

  利用Doublechecked locking 兩次同步中間變量解決上邊的問題

  (下邊這段話我只能簡單的理解翻譯過來不好所以保留原文list 是上邊的代碼list 是下邊的

  The code in Listing doesnt work because of the current definition of the memory model

  The Java Language Specification (JLS) demands that code within a synchronized block

  not be moved out of a synchronized block However it does not say that

  code not in a synchronized block cannot be moved into a synchronized block

  A JIT compiler would see an optimization opportunity here

  This optimization would remove the code at

  // and the code at // combine it and generate the code shown in Listing :)

  

  list

  public static Singleton getInstance()

  {

  if (instance == null)

  {

  synchronized(Singletonclass) {      //

  Singleton inst = instance;         //

  if (inst == null)

  {

  synchronized(Singletonclass) {  //

  //inst = new Singleton();      //

  instance = new Singleton();

  }

  //instance = inst;               //

  }

  }

  }

  return instance;

  }

  If this optimization takes place you have the same outoforder write problem we discussed earlier

  如果這個優化發生將再次發生上邊提到的問題取得沒有實例化完成的數據

  

  以下部分為了避免我翻譯錯誤誤導打家保留原文

  Another idea is to use the keyword volatile for the variables inst and instance

  According to the JLS (see Resources) variables declared volatile are supposed to

  be sequentially consistent and therefore not reordered

  But two problems occur with trying to use volatile to fix the problem with

  doublechecked locking:

  The problem here is not with sequential consistency

  Code is being moved not reordered

  Many JVMs do not implement volatile correctly regarding sequential consistency anyway

  The second point is worth expanding upon Consider the code in Listing :

  Listing Sequential consistency with volatile

  class test

  {

  private volatile boolean stop = false;

  private volatile int num = ;

  public void foo()

  {

  num = ;    //This can happen second

  stop = true;  //This can happen first

  //

  }

  public void bar()

  {

  if (stop)

  num += num;  //num can == !

  }

  //

  }

  According to the JLS because stop and num are declared volatile

  they should be sequentially consistent This means that if stop is ever true

  num must have been set to

  However because many JVMs do not implement the sequential consistency feature of volatile

  you cannot count on this behavior

  Therefore if thread called foo and thread called bar concurrently

  thread might set stop to true before num is set to

  This could lead thread to see stop as true but num still set to

  There are additional problems with volatile and the atomicity of bit variables

  but this is beyond the scope of this article

  See Resources for more information on this topic

  簡單的理解上邊這段話使用volatile有可能能解決問題volatile被定義用來保正一致性但是很多虛擬機並沒有很好的實現volatile所以使用它也會存在問題

  最終的解決方案

  (單態模式使用同步方法

  (放棄同步使用一個靜態變量如下

  class Singleton

  {

  private Vector v;

  private boolean inUse;

  private static Singleton instance = new Singleton();

  private Singleton()

  {

  v = new Vector();

  inUse = true;

  //

  }

  public static Singleton getInstance()

  {

  return instance;

  }

  }

  但使用靜態變量也會存在問題問題見 這篇文章

  而且如在文章開頭提到的使用EJB跨服務器跨JVM的情況下單態更是問題

  好了是不是感覺單態模式根本沒法使用了其實上邊都是特殊情況這中特殊情況的出現是有條件的只要根據你的具體應用回避一些就能解決問題所以單態還是可以使用的但是在使用前慎重自己考慮好自己的情況適合哪種情況


From:http://tw.wingwit.com/Article/program/Java/gj/201311/27326.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.