淺談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!"); } }