您好,登錄后才能下訂單哦!
文章首發:
創建型模式:單例模式
姓名:單例模式
英文名:Singleton Pattern
價值觀:我的生活我主宰(只允許自己實例化,不愿意被其他對象實例化)
個人介紹:
Ensure a class has only one instance, and provide a global point of access to it.(確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例。)
(來自《設計模式之禪》)
這里的關注點有 3 個,分別是:
我們腦洞大開來用一個故事講解一番。
小明家里有一輛小汽車,具體什么牌子就不知道了,咱也不關注,反正他家里就這么一輛車,小明比較懶,只要一出門都會開車,例如去旅游、去學校、去聚會都會開車去。下面模擬小明出去的場景。
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 種最簡單的實現方式,不然會頭暈的。
希望文章對您有所幫助,設計模式系列會持續更新,感興趣的同學可以關注公眾號LieBrother,第一時間獲取文章推送閱讀,也可以一起交流,交個朋友。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。