
1、前言
1-1、 概述
設計模式 = 某類特定問題的解決方案,那么單例模式是解決什么問題的解決方案呢?
定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
含義:單例 = 一個實例
解決的問題:在任何時間內只有一個類實例存在的模式
解決方法:保證一個類只有一個實例化對象,並提供一個全局訪問入口
本質:控制實例的數量
注意:要合理的使用單例,避免單例成為瓶頸
英文:Singleton
類型:創建類模式
1-2、問題引入
模擬網站訪問數量統計功能:
package com.designmode.singleton;
/**
* 網站計數器
*/
class WebCounter {
private int count = 0;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
/**
* 用戶訪問
*/
class Visitor{
public WebCounter webCounter;
public Visitor(WebCounter mwebCounter){
webCounter = mwebCounter;
}
//訪問
public void visit(){
webCounter.setCount(webCounter.getCount()+1);;
}
}
/**
* 模擬用戶訪問網站
*/
public class SingleTest{
public static void main(String[] args){
WebCounter webCounter1 = new WebCounter();
WebCounter webCounter2 = new WebCounter();
Visitor visitor1 = new Visitor(webCounter1);
Visitor visitor2 = new Visitor(webCounter2);
System.out.println("是不是同一個網站?");
if(webCounter1.equals(webCounter2)){
System.out.println("是");
}else {
System.out.println("不是");
}
//visitor1訪問該網站
visitor1.visit();
System.out.println("訪問量:" + webCounter1.getCount());
//visitor2訪問該網站
visitor2.visit();
System.out.println("訪問量:" + webCounter2.getCount());
}
}
結果:
是不是同一個網站? 不是 訪問量:1 訪問量:1
從結果看,兩個人訪問的不是一個網站實例,其實我們要實現的邏輯是,訪問同一個網站,計算訪問量,這顯然是不符合我們想要的
2、介紹
2-1、分析引入的問題
沖突:從上面的結果可以看出,網站計數器類操作的明顯不是同一個實例
目標:所有訪問者操作同一個網站計數器類
單例模式就是為了解決這類問題的解決方案:實現一個類只有一個實例化對象,並提供一個全局訪問入口
2-2、解決引入的問題
解決:改造一下網站計數器類的實現代碼
package com.designmode.singleton;
/**
* 網站計數器
*/
class WebCounter {
private int count = 0;
private static WebCounter instance = new WebCounter();
private WebCounter() {
}
public static WebCounter getInstance() {
return instance;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
/**
* 用戶訪問
*/
class Visitor{
public WebCounter webCounter;
public Visitor(WebCounter mwebCounter){
webCounter = mwebCounter;
}
//訪問
public void visit(){
webCounter.setCount(webCounter.getCount()+1);;
}
}
/**
* 模擬用戶訪問網站
*/
public class SingleTest{
public static void main(String[] args){
WebCounter webCounter1 = WebCounter.getInstance();
WebCounter webCounter2 = WebCounter.getInstance();
Visitor visitor1 = new Visitor(webCounter1);
Visitor visitor2 = new Visitor(webCounter2);
System.out.println("是不是同一個網站?");
if(webCounter1.equals(webCounter2)){
System.out.println("是");
}else {
System.out.println("不是");
}
//visitor1訪問該網站
visitor1.visit();
System.out.println("訪問量:" + webCounter1.getCount());
//visitor2訪問該網站
visitor2.visit();
System.out.println("訪問量:" + webCounter2.getCount());
}
}
再來看一下結果:
是不是同一個網站? 是 訪問量:1 訪問量:2
這次是對的!!!
2-3、實現原理
引入單例模式:一般實現方式
public class Singleton {
//1. 創建私有變量 instance(用以記錄 Singleton 的唯一實例)
//2. 內部進行實例化
private static Singleton instance = new Singleton();
//3. 把類的構造方法私有化,不讓外部調用構造方法實例化
private Singleton() {
}
//4. 定義公有方法提供該類的全局唯一訪問點
//5. 外部通過調用getInstance()方法來返回唯一的實例
public static Singleton getInstance() {
return instance;
}
}
2-4、優點、缺點
優點:
1.在單例模式中,活動的單例只有一個實例,對單例類的所有實例化得到的都是相同的一個實例。這樣就防止其它對象對自己的實例化,確保所有的對象都訪問一個實例
2.單例模式具有一定的伸縮性,類自己來控制實例化進程,類在實例化進程上有相應的伸縮性
3.提供了對唯一實例的訪問入口
4.由於在系統內存中只存在一個對象,因此可以節約系統資源,當需要頻繁創建和銷毀的對象時單例模式無疑可以提高系統的性能
5.允許可變數目的實例(可以根據實際情況需要,在單例模式的基礎上擴展做出雙例模式、多例模式)
6.避免對共享資源的多重占用
缺點:
1.不適用於變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態
2.由於單利模式中沒有抽象層,因此單例類的擴展有很大的困難。
3.單例類的職責過重,在一定程度上違背了“單一職責原則”
4.濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認為是垃圾而被回收,這將導致對象狀態的丟失
3、實現
3-1、餓漢模式、懶漢模式
餓漢模式:
package com.designmode;
/**
* 餓漢模式(最簡單的形式)
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
應用場景:
要求直接在應用啟動時加載並初始化
單例對象要求初始化速度非常快
懶漢模式:
package com.designmode;
/**
* 懶漢模式(最簡單的形式)
*/
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton newInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
應用場景:
單例初始化的操作耗時比較長(可以相應縮短應用啟動時間)
單例只是在某個特定場景的情況下才會被使用,即按需延遲加載單例
對比:
餓漢式:自動進行單例的初始化
懶漢式:有需要的時候才手動調用getInstance()進行單例的初始化操作
3-2、多線程下的實現
在多線程的情況下:
“餓漢式單例模式”:適用,因為JVM只會加載一次單例類
“懶漢式單例模式”:不適用,因為“懶漢式”在創建單例時是線程不安全的,多個線程可能會並發調用 getInstance 方法從而出現重復創建單例對象的問題
下面有幾個解決方案:
方案1:同步鎖
//使用同步鎖 synchronized (Singleton.class) 防止多線程同時進入造成instance被多次實例化
package com.designmode;
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
return instance;
}
}
方案2:雙重校驗鎖
//在同步鎖的基礎上( synchronized (Singleton.class) 外)添加了一層if,這是為了在Instance已經實例化后下次進入不必執行 synchronized (Singleton.class) 獲取對象鎖,從而提高性能
package com.designmode;
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
方案3:靜態內部類
//在JVM進行類加載的時候會保證數據是同步的,我們采用內部類實現:在內部類里面去創建對象實例
//只要應用中不使用內部類 JVM 就不會去加載這個單例類,也就不會創建單例對象,從而實現“懶漢式”的延遲加載和線程安全。
package com.designmode;
public class Singleton {
//在裝載該內部類時才會去創建單例對象
private static class Singleton2{
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return Singleton2.instance;
}
}
方案4:枚舉類型
//最簡潔、易用的單例實現方式,(《Effective Java》推薦)
package com.designmode;
public enum Singleton{
//定義一個枚舉的元素,它就是Singleton的一個實例
INSTANCE;
private Singleton() {
}
public void doSomething(){
}
}
調用方式:
Singleton.INSTANCE.doSomething();
4、總結
設計模式 = 某類特定問題的解決方案,那么單例模式是解決什么問題的解決方案呢?
含義:單例 = 一個實例
解決的問題:在任何時間內只有一個類實例存在的模式
解決方法:保證一個類只有一個實例化對象,並提供一個全局訪問入口
本質:控制實例的數量
注意:要合理的使用單例,避免單例成為瓶頸
PS:源碼地址 https://github.com/JsonShare/DesignPattern/tree/master
PS:原文地址 http://www.cnblogs.com/JsonShare/p/7093947.html
