單例設計模式(Singleton Pattern)
是最簡單且常見的設計模式之一,主要做用是提供一個全局訪問且只實例化一次的對象,避免多實例對象的狀況下引發邏輯性錯誤(實例化數量可控)
...java
<!-- more -->git
Java中,單例模式
主要分三種:懶漢式單例、餓漢式單例、登記式單例三種。設計模式
ClassLoad
的時候就已經實例化好,該操做過於風騷會形成資源浪費特色安全
案例性能優化
任務管理器
中看到,但始終只能打開一個任務管理器
,它在Windows操做系統中是具有惟一性的,由於彈多個框屢次採集數據浪費性能不說,採集數據存在偏差那就有點逗比了不是麼...打印機後臺處理程序
注意事項微信
非線程安全
方式的建立須要特別注意,且在使用的時候儘可能根據場景選取較優的,線程安全了還須要去考慮性能問題。單一職責原則
。第一種:單一檢查(懶漢)非線程安全多線程
public class LazyLoadBalancer { private static LazyLoadBalancer loadBalancer; private List<String> servers = null; private LazyLoadBalancer() { servers = new ArrayList<>(); } public void addServer(String server) { servers.add(server); } public String getServer() { Random random = new Random(); int i = random.nextInt(servers.size()); return servers.get(i); } public static LazyLoadBalancer getInstance() { // 第一步:假設T1,T2兩個線程同時進來且知足 loadBalancer == null if (loadBalancer == null) { // 第二步:那麼 loadBalancer 即會被實例化2次 loadBalancer = new LazyLoadBalancer(); } return loadBalancer; } public static void main(String[] args) { LazyLoadBalancer balancer1 = LazyLoadBalancer.getInstance(); LazyLoadBalancer balancer2 = LazyLoadBalancer.getInstance(); System.out.println("hashCode:"+balancer1.hashCode()); System.out.println("hashCode:"+balancer2.hashCode()); balancer1.addServer("Server 1"); balancer2.addServer("Server 2"); IntStream.range(0, 5).forEach(i -> System.out.println("轉發至:" + balancer1.getServer())); } }
日誌dom
hashCode:460141958 hashCode:460141958 轉發至:Server 2 轉發至:Server 2 轉發至:Server 2 轉發至:Server 1 轉發至:Server 2
分析: 在單線程環境一切正常,balancer1
和balancer2
兩個對象的hashCode
如出一轍,由此能夠判斷出堆棧中只有一分內容,不過該代碼塊中存在線程安全隱患
,由於缺少競爭條件,多線程環境資源競爭的時候就顯得不太樂觀了,請看上文代碼註釋內容性能
第二種:無腦上鎖(懶漢)線程安全,性能較差,第一種升級版優化
public synchronized static LazyLoadBalancer getInstance() { if (loadBalancer == null) { loadBalancer = new LazyLoadBalancer(); } return loadBalancer; }
分析: 毫無疑問,知道synchronized
關鍵字的都知道,同步方法
在鎖沒釋放以前,其它線程都在排隊候着呢,想不安全都不行啊,但在安全的同時,性能方面就顯得短板了,我就初始化一次,你丫的每次來都上個鎖,不累的嗎(不要緊,它是爲了第三種作鋪墊的)..
第三種:雙重檢查鎖(DCL),徹底就是前兩種的結合體啊,有木有,只是將同步方法升級成了同步代碼塊
//劃重點了 **volatile** private volatile static LazyLoadBalancer loadBalancer; public static LazyLoadBalancer getInstance() { if (loadBalancer == null) { synchronized (LazyLoadBalancer.class) { if (loadBalancer == null) { loadBalancer = new LazyLoadBalancer(); } } } return loadBalancer; }
1.假設new LazyLoadBalancer()
加載內容過多
2.因重排而致使loadBalancer
提早不爲空
3.正好被其它線程觀察到對象非空直接返回使用
mem = allocate(); //LazyLoadBalancer 分配內存 instance = mem; //注意當前實例已經不爲空了 initByLoadBalancer(instance); //可是還有其它實例未初始化
存在問題: 首先咱們必定要清楚,DCL
是不能保證線程安全
的,稍微瞭解過JVM的就清楚,對比C/C++
它始終缺乏一個正式的內存模型
,因此爲了提高性能,它還會作一次指令重排操做
,這個時候就會致使loadBalancer
提早不爲空,正好被其它線程觀察到對象非空直接返回使用(但實際還有部份內容沒加載完成)
解決方案: 用volatile
修飾loadBalancer
,由於volatile
修飾的成員變量能夠確保多個線程都可以順序處理,它會屏蔽JVM指令重排帶來的性能優化
。
volatile詳解:http://blog.battcn.com/2017/10/18/java/thread/thread-volatile/
第四種:Demand Holder (懶漢)線程安全,推薦使用
private LazyLoadBalancer() {} private static class LoadBalancerHolder { //在JVM中 final 對象只會被實例化一次,沒法修改 private final static LazyLoadBalancer INSTANCE = new LazyLoadBalancer(); } public static LazyLoadBalancer getInstance() { return LoadBalancerHolder.INSTANCE; }
分析: 在Demand Holder
中,咱們在LazyLoadBalancer
裏增長一個靜態(static)內部類,在該內部類中建立單例對象,再將
該單例對象經過getInstance()
方法返回給外部使用,因爲靜態單例對象沒有做爲LazyLoadBalancer
的成員變量直接實例化,類加載時並不會實例化LoadBalancerHolder
,所以既能夠實現延遲加載,又能夠保證線程安全,不影響系統性能(居家旅行必備良藥啊)
第五種:枚舉特性(懶漢)線程安全
enum Lazy { INSTANCE; private LazyLoadBalancer loadBalancer; //枚舉的特性,在JVM中只會被實例化一次 Lazy() { loadBalancer = new LazyLoadBalancer(); } public LazyLoadBalancer getInstance() { return loadBalancer; } }
分析: 相比上一種,該方式一樣是用到了JAVA特性:枚舉類保證只有一個實例(即便使用反射機制也沒法屢次實例化一個枚舉量)
第六種:餓漢單例(天生線程安全),
public class EagerLoadBalancer { private final static EagerLoadBalancer INSTANCE = new EagerLoadBalancer(); private EagerLoadBalancer() {} public static EagerLoadBalancer getInstance() { return INSTANCE; } }
分析: 利用ClassLoad
機制,在加載時進行實例化,同時靜態方法只在編譯期間執行一次初始化,也就只有一個對象。使用的時候已被初始化完畢能夠直接調用,可是相比懶漢模式
,它在使用的時候速度最快,但這玩意就像本身挖的坑哭着也得跳,你不用也得初始化一份在內存中佔個坑...
全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter2/battcn-singleton
微信公衆號:battcn
(歡迎調戲)