我的博客原文:
建立型模式:單例模式java
姓名:單例模式設計模式
英文名:Singleton Pattern多線程
價值觀:個人生活我主宰(只容許本身實例化,不肯意被其餘對象實例化)併發
我的介紹:ide
Ensure a class has only one instance, and provide a global point of access to it.(確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例。)
(來自《設計模式之禪》)工具
這裏的關注點有 3 個,分別是:線程
咱們腦洞大開來用一個故事講解一番。設計
小明家裏有一輛小汽車,具體什麼牌子就不知道了,咱也不關注,反正他家裏就這麼一輛車,小明比較懶,只要一出門都會開車,例如去旅遊、去學校、去聚會都會開車去。下面模擬小明出去的場景。code
class Car { public void run() { System.out.println("走。。。。"); } } class XiaoMing { public Car travel() { System.out.println("小明去旅遊"); Car car = new Car(); car.run(); return car; } public Car goToSchool() { System.out.println("小明去學校"); Car car = new Car(); car.run(); return car; } public Car getTogether() { System.out.println("小明參加聚會"); Car car = new Car(); car.run(); return car; } } public class SingletonErrorTest { public static void main(String[] args) { XiaoMing xiaoMing = new XiaoMing(); Car car1 = xiaoMing.travel(); Car car2 = xiaoMing.goToSchool(); Car car3 = xiaoMing.getTogether(); } }
上面小汽車只有一個方法,就是走。小明去旅遊、去學校、參加聚會都開着他惟一的一輛汽車車去。是否是有人有疑問?爲何每一個方法都返回 Car 對象?其實只是想在下面作一次檢查,檢查小明去旅遊、去學校和參加聚會的車是否是同一輛。下面是檢查代碼:對象
System.out.println("car1 == car2 ? " + (car1 == car2)); System.out.println("car2 == car3 ? " + (car2 == car3));
最終結果是啥?很明顯是 2 個 false。小明去旅遊、去學校和參加聚會的車都不相同,小明不是隻有 1 輛車?關鍵在於 Car car = new Car();
這一句代碼,其實這一句是建立一輛車,每次都從新建立一輛。那應該怎麼實現小明只有一輛車呢?這時候就引入了單例模式。
上面咱們說到了單例模式須要具有的 3 個點:只有 1 個實例,很顯然,上面的代碼不止 1 個實例,而是有 3 個 Car 實例;自行實例化,Car 自己沒有主動實例化,而是在小明須要用到的時候才實例化;向整個系統提供這個實例,由於 Car 沒有主動實例化,因此它無法向外部暴露提供本身出來。
咱們的代碼徹底不符合單例模式的要求。咱們要經過修改,使之符合單例模式的 3 個要點。首先須要實現的是第 2 點,把 Car 實例化從小明轉爲 Car 自己,以下代碼
class Car1{ private static Car1 car1 = new Car1(); private Car1() { } public void run(){ System.out.println("走。。。。"); } }
上面代碼使用 private 修飾構造方法,使得 Car1 不能被其餘使用方實例化,經過 Car1 car1 = new Car1();
主動實例化本身。
接下來再實現第 3 點,向整個系統暴露這個實例,也就是暴露它本身。每一個使用方都調用 Car1.getInstance()
方法來獲取實例。
class Car1{ private static Car1 car1 = new Car1(); public static Car1 getInstance() { return car1; } private Car1() { } public void run(){ System.out.println("走。。。。"); } }
上面代碼就實現了單例模式的 2 和 3 要點,第 1 要點要怎麼實現呢?告訴你,不用實現,只要知足了 2 和 3 要點就能夠,第 1 要點是用來檢驗是不是單例模式的好思路。咱們檢驗一下
class Car1{ private static Car1 car1 = new Car1(); public static Car1 getInstance() { return car1; } private Car1() { } public void run(){ System.out.println("走。。。。"); } } class XiaoMing1 { public Car1 travel() { System.out.println("小明去旅遊"); Car1 car = Car1.getInstance(); car.run(); return car; } public Car1 goToSchool() { System.out.println("小明去學校"); Car1 car = Car1.getInstance(); car.run(); return car; } public Car1 getTogether() { System.out.println("小明參加聚會"); Car1 car = Car1.getInstance(); car.run(); return car; } } public class SingletonRightHungryTest { public static void main(String[] args) { XiaoMing1 xiaoMing1 = new XiaoMing1(); Car1 car1 = xiaoMing1.travel(); Car1 car2 = xiaoMing1.goToSchool(); Car1 car3 = xiaoMing1.getTogether(); System.out.println("car1 == car2 ? " + (car1 == car2)); System.out.println("car2 == car3 ? " + (car2 == car3)); } }
上面代碼最後兩行打印出來的結果是啥?是咱們想要的:2 個 true。說明小明這幾回外出開的車都是同一輛。這是最簡單的單例模式的實現方式,咱們常常稱做餓漢式單例模式。爲何起這麼古怪的名字呢?其實和對應的懶漢式單例模式有關,這是 2 個實現方式的差異,餓漢式單例模式實現方式在類加載到內存的時候,就建立好對象了,而懶漢式則是在第一次使用的時候才建立對象,也就是把建立對象的時機從加載延遲到第一次使用,因此纔有懶餓之分。
下面咱們來看怎麼實現懶漢式單例模式。先描述一下場景:小明尚未汽車,他也不知道何時要買汽車,忽然某一天,他想去旅遊,以爲是時候買輛車了,而後他就買車去旅遊了,旅遊回來又開車去學校和參加聚會。
class Car2{ private static Car2 car2; public static synchronized Car2 getInstance() { if (null == car2) { System.out.println("買車啦。。。"); car2 = new Car2(); } return car2; } private Car2() { } public void run(){ System.out.println("走。。。。"); } } class XiaoMing2 { public Car2 travel() { System.out.println("小明去旅遊"); Car2 car = Car2.getInstance(); car.run(); return car; } public Car2 goToSchool() { System.out.println("小明去學校"); Car2 car = Car2.getInstance(); car.run(); return car; } public Car2 getTogether() { System.out.println("小明參加聚會"); Car2 car = Car2.getInstance(); car.run(); return car; } } public class SingletonRightLazyTest { public static void main(String[] args) { XiaoMing2 xiaoMing2 = new XiaoMing2(); Car2 car1 = xiaoMing2.travel(); Car2 car2 = xiaoMing2.goToSchool(); Car2 car3 = xiaoMing2.getTogether(); System.out.println("car1 == car2 ? " + (car1 == car2)); System.out.println("car2 == car3 ? " + (car2 == car3)); } } 小明去旅遊 買車啦。。。 走。。。。 小明去學校 走。。。。 小明參加聚會 走。。。。 car1 == car2 ? true car2 == car3 ? true
上面附帶了打印出來的結果,小明要去旅遊的時候,纔去買車。這就是懶漢式單例模式的實現方式。
要注意懶漢式單例模式有個很關鍵的一點就是 getInstance() 方法帶上了 synchronized,這個是爲何呢?
首先得了解關鍵字 synchronized 的做用是什麼:用於修飾執行方法同步,也就是說多線程併發的狀況下,在一個時間點,只容許一個線程執行這個方法。
不加上這個會有什麼結果?在多線程併發狀況下,若是有 2 個線程同時執行到 if(null == car2),那麼都判斷爲 true,這時 2 個線程都會執行 car2 = new Car2(),這樣子就不是單例了。
單例模式能夠說是設計模式中最簡單的一個,也是在工做中不少場景下常常用到的,好比:項目的配置文件加載、各類工具類等等。咱們對於單例模式最重要的一點就是要考慮多線程併發,沒有考慮這點就容易引起單例對象不單例的狀況。而單例給咱們帶來最大的好處就是節約內存。
上面實現的兩種方法是單例模式中最最最簡單的 2 種實現,相信也是用得最多的實現方式。網上有很多網友分享了單例模式的不少種實現方法,你們也能夠去了解,在瞭解以前務必已經搞懂文中這 2 種最簡單的實現方式,否則會頭暈的。
參考資料:《大話設計模式》、《Java設計模式》、《設計模式之禪》、《研磨設計模式》、《Head First 設計模式》
但願文章對您有所幫助,設計模式系列會持續更新,感興趣的同窗能夠關注公衆號,第一時間獲取文章推送閱讀,也能夠一塊兒交流,交個朋友。