程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 淺談Java編程中的單例設計形式

淺談Java編程中的單例設計形式

編輯:關於JAVA

淺談Java編程中的單例設計形式。本站提示廣大學習愛好者:(淺談Java編程中的單例設計形式)文章只能為提供參考,不一定能成為您想要的結果。以下是淺談Java編程中的單例設計形式正文


寫軟件的時刻常常須要用到打印日記功效,可以贊助你調試和定位成績,項目上線後還可以贊助你剖析數據。然則Java原生帶有的System.out.println()辦法卻很少在真實的項目開辟中應用,乃至像findbugs等代碼檢討對象還會以為應用System.out.println()是一個bug。

為何作為Java老手神器的System.out.println(),到了真正項目開辟傍邊會被鄙棄呢?其實只需細細剖析,你就會發明它的許多弊病。好比弗成掌握,一切的日記都邑在項目上線後照舊打印,從而下降運轉效力;又或許不克不及將日記記載到當地文件,一旦打印被消除,日記將再也找不回來;再或許打印的內容沒有Tag辨別,你將很難鑒別這一行日記是在哪一個類裡打印的。

你的leader也不是傻瓜,用System.out.println()的各項弊病他也清清晰楚,是以他明天給你的義務就是制造一個日記對象類,來供給更好的日記功效。不外你的leader人還不錯,並沒讓你一開端就完成一個具有各項功效的牛逼日記對象類,只須要一個可以或許掌握打印級其余日記對象就好。

這個需求對你來講其實不難,你連忙就開端著手編寫了,並很快完成了第一個版本:

  public class LogUtil { 
    public final int DEBUG = 0; 
   
    public final int INFO = 1; 
   
    public final int ERROR = 2; 
   
    public final int NOTHING = 3; 
   
    public int level = DEBUG; 
   
    public void debug(String msg) { 
      if (DEBUG >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void info(String msg) { 
      if (INFO >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void error(String msg) { 
      if (ERROR >= level) { 
        System.out.println(msg); 
      } 
    } 
  } 



經由過程這個類來打印日記,只須要掌握level的級別,便可以自在地掌握打印的內容。好比如今項目處於開辟階段,就將level設置為DEBUG,如許一切的日記信息都邑被打印。而項目假如上線了,可以把level設置為INFO,如許就只能看到INFO及以下級其余日記打印。假如你只想看到毛病日記,便可以把level設置為ERROR。而假如你開辟的項目是客戶端版本,不想讓任何日記打印出來,可以將level設置為NOTHING。打印的時刻只須要挪用:

  new LogUtil().debug("Hello World!"); 



你迫在眉睫地將這個對象引見給你的leader,你的leader聽完你的引見後說:“好樣的,往後年夜伙都用你寫的這個對象來打印日記了!”

可是沒過量久,你的leader找到你來反應成績了。他說固然這個對象好用,可是打印這類工作是不辨別對象的,這裡每次須要打印日記的時刻都須要new出一個新的LogUtil,太占用內存了,願望你可以將這個對象改成用單例形式完成。

你以為你的leader說的很有事理,並且你也正想趁這個機遇演習應用一下設計形式,因而你寫出了以下的代碼(ps:這裡代碼是我本身完成的,並且我開端確切沒留意線程同步成績):

  public class LogUtil { 
    private static LogUtil logUtilInstance; 
   
    public final int DEBUG = 0; 
   
    public final int INFO = 1; 
   
    public final int ERROR = 2; 
   
    public final int NOTHING = 3; 
   
    public int level = DEBUG; 
   
    private LogUtil() { 
   
    } 
   
    public static LogUtil getInstance() { 
      if (logUtilInstance == null) { 
        logUtilInstance = new LogUtil(); 
      } 
   
      return logUtilInstance; 
    } 
   
    public void debug(String msg) { 
      if (DEBUG >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void info(String msg) { 
      if (INFO >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void error(String msg) { 
      if (ERROR >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public static void main(String[] args) { 
      LogUtil.getInstance().debug("Hello World!"); 
    } 
  } 


起首將LogUtil的結構函數公有化,如許就沒法應用new症結字來創立LogUtil的實例了。然後應用一個sLogUtil公有靜態變量來保留實例,並供給一個私有的getInstance辦法用於獲得LogUtil的實例,在這個辦法外面斷定假如sLogUtil為空,就new出一個新的LogUtil實例,不然就直接前往sLogUtil。如許便可以包管內存傍邊只會存在一個LogUtil的實例了。單例形式落成!這時候打印日記的代碼須要改成以下方法:

  LogUtil.getInstance().debug("Hello World");  


你將這個版本展現給你的leader瞧,他看後笑了笑,說:“固然這看似是完成了單例形式,可是還存在著bug的哦。
你滿腹懷疑,單例形式不都是如許完成的嗎?還會有甚麼bug呢?

你的leader提醒你,應用單例形式就是為了讓這個類在內存中只能有一個實例的,可是你有斟酌到在多線程中打印日記的情形嗎?以下面代碼所示:

  public static LogUtil getInstance() { 
    if (logUtilInstance == null) { 
      logUtilInstance = new LogUtil(); 
    } 
   
    return logUtilInstance; 
  } 


假如如今有兩個線程同時在履行getInstance辦法,第一個線程剛履行完第2行,還沒履行第3行,這個時刻第二個線程履行到了第2行,它會發明sLogUtil照樣null,因而進入到了if斷定外面。如許你的單例形式就掉敗了,由於創立了兩個分歧的實例。
你豁然開朗,不外你的思想異常快,連忙就想到懂得決方法,只須要給辦法加上同步鎖便可以了,代碼以下:

  public synchronized static LogUtil getInstance() { 
    if (logUtilInstance == null) { 
      logUtilInstance = new LogUtil(); 
    } 
   
    return logUtilInstance; 
  } 


如許,統一時辰只許可有一個線程在履行getInstance外面的代碼,如許就有用地處理了下面會創立兩個實例的情形。
你的leader看了你的新代碼後說:“恩,不錯。這確切處理了有能夠創立兩個實例的情形,然則這段代碼照樣有成績的。”

你重要了起來,怎樣還會有成績啊?

你的leader笑笑:“不消重要,此次不是bug,只是機能上可以優化一些。你看一下,假如是在getInstance辦法上加了一個synchronized,那末我每次去履行getInstace辦法的時刻都邑遭到同步鎖的影響,如許運轉的效力會下降,其實只須要在第一次創立LogUtil實例的時刻加上同步鎖就行了。我來教你一下怎樣把它優化的更好。”

起首將synchronized症結字從辦法聲明中去除,把它參加到辦法體傍邊:

  public static LogUtil getInstance() { 
    if (logUtilInstance == null) { 
      synchronized (LogUtil.class) { 
        if (logUtilInstance == null) { 
          // 這裡是必需的,由於有能夠兩個過程同時履行到synchronized之前 
          logUtilInstance = new LogUtil(); 
        } 
      } 
    } 
   
    return logUtilInstance; 
  } 


代碼改成如許以後,只要在sLogUtil還沒被初始化的時刻才會進入到第3行,然後加上同步鎖。等sLogUtil一但初始化完成了,就再也走不到第3行了,如許履行getInstance辦法也不會再遭到同步鎖的影響,效力上會有必定的晉升。
你不由自主贊賞到,這辦法真奇妙啊,能想得出來其實是太聰慧了。

你的leader立時謙遜起來:“這類辦法叫做兩重鎖定(Double-Check Locking),可不是我想出來的,更多的材料你可以在網上查一查。”

其其實java裡完成單例我更習氣用餓漢形式

懶漢式的特色是延遲加載,實例直到用到的時刻才會加載

餓漢式的特色是一開端就加載了,所以每次用到的時刻直接前往便可(我更推舉這一種,由於不須要斟酌太多線程平安的成績,固然懶漢式是可以經由過程上文所說的兩重鎖定處理同步成績的)

用餓漢式完成日記記載的代碼以下:

  public class LogUtil { 
    private static final LogUtil logUtilInstance = new LogUtil(); 
   
    public final int DEBUG = 0; 
   
    public final int INFO = 1; 
   
    public final int ERROR = 2; 
   
    public final int NOTHING = 3; 
   
    public int level = DEBUG; 
   
    private LogUtil() { 
   
    } 
   
    public static LogUtil getInstance() { 
      return logUtilInstance; 
    } 
   
    public void debug(String msg) { 
      if (DEBUG >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void info(String msg) { 
      if (INFO >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void error(String msg) { 
      if (ERROR >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public static void main(String[] args) { 
      logUtil.getInstance().debug("Hello World!"); 
    } 
  } 


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved