<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一個(gè)類只能有一個(gè)對(duì)象?

          共 8581字,需瀏覽 18分鐘

           ·

          2021-09-06 20:52

          1、什么是單例模式

          Ensure a class has only one instance, and provide a global point of access to it.

          采取一定的辦法保證在整個(gè)軟件系統(tǒng)中,確保對(duì)于某個(gè)類只能存在一個(gè)實(shí)例。單例模式有如下三個(gè)特點(diǎn):

          ①、單例類只能有一個(gè)實(shí)例

          ②、單例類必須自己創(chuàng)建自己的實(shí)例

          ③、單例類必須提供外界獲取這個(gè)實(shí)例的方法

          2、單例類的設(shè)計(jì)思想(Singleton)

          ①、外界不能創(chuàng)建這個(gè)類的實(shí)例,那么必須將構(gòu)造器私有化。

          public class Singleton {
           //構(gòu)造器私有化
           private Singleton(){
            
           }
          }

          ②、單例類必須自己創(chuàng)建自己的實(shí)例,不能允許在類的外部修改內(nèi)部創(chuàng)建的實(shí)例。

          比如將這個(gè)實(shí)例用 private 聲明。為了外界能訪問到這個(gè)實(shí)例,我們還必須提供 get 方法得到這個(gè)實(shí)例。因?yàn)橥饨绮荒?new 這個(gè)類,所以我們必須用 static 來修飾字段和方法。

          //在類的內(nèi)部自己創(chuàng)建實(shí)例
          private static Singleton singleton = new Singleton();

          //提供get 方法以供外界獲取單例
          public Singleton getInstance(){
            return singleton;
          }

          ③、是否支持延遲加載?

          有些情況下,創(chuàng)建某個(gè)實(shí)例耗時(shí)長(zhǎng),占用資源多,用的時(shí)候也少,我們會(huì)考慮在用到的時(shí)候才會(huì)去創(chuàng)建,這就是延遲加載。

          但有些情況,按照 fail-fast 的設(shè)計(jì)原則(有問題及早暴露),比如某個(gè)實(shí)例占用資源很多,如果延遲加載,會(huì)在程序運(yùn)行一段時(shí)間后OOM,如果在程序啟動(dòng)的時(shí)候就創(chuàng)建這個(gè)實(shí)例,我們就可以立即去修復(fù),不會(huì)導(dǎo)致程序運(yùn)行之后的系統(tǒng)奔潰。

          所以,是否支持延遲加載需要結(jié)合實(shí)際情況考慮。

          ④、保證線程安全

          這個(gè)是一定要考慮的,如果你寫的單例類存在線程安全問題,那就是偽單例了。

          3、單例類的幾種實(shí)現(xiàn)方式

          3.1 單例模式之餓漢模式

          public class Singleton {
           //構(gòu)造器私有化
           private Singleton(){
            
           }
           //在類的內(nèi)部自己創(chuàng)建實(shí)例
           private static Singleton singleton = new Singleton();

           //提供get 方法以供外界獲取單例
           public static Singleton getInstance(){
            return singleton;
           }
           
          }

          測(cè)試:

          public static void main(String[] args) {
           Singleton s1 = Singleton.getInstance();
           Singleton s2 = Singleton.getInstance();
           System.out.println(s1.equals(s2)); //true
          }


          這種模式在類加載的時(shí)候?qū)嵗?singleton 就已經(jīng)創(chuàng)建并初始化好了,所以是線程安全的。

          不過這種模式不支持延遲加載,有可能這個(gè)實(shí)例化過程很長(zhǎng),那么就會(huì)加大類裝載的時(shí)間;有可能這個(gè)實(shí)例現(xiàn)階段根本用不到,那么創(chuàng)建了這個(gè)實(shí)例,也會(huì)浪費(fèi)內(nèi)存。但是還是我們前面說的,是否支持延遲加載,需要結(jié)合實(shí)際情況考慮。

          3.2 單例模式之懶漢模式(線程不安全)

          //懶漢模式
          public class Singleton {
           //構(gòu)造器私有化
           private Singleton(){
            
           }
           //在類的內(nèi)部自己創(chuàng)建實(shí)例的引用
           private static Singleton singleton = null;

           //提供get 方法以供外界獲取單例
           public static Singleton getInstance(){
            if(singleton == null){
             singleton = new Singleton();
            }
            return singleton;
           }
           
          }

          這種方法達(dá)到了 lazy-loading 的效果,即我們?cè)诘谝淮涡枰玫竭@個(gè)單例的時(shí)候,才回去創(chuàng)建它的實(shí)例,以后再需要就可以不用創(chuàng)建,直接獲取了。但是這種設(shè)計(jì)在多線程的情況下是不安全的。

          我們可以創(chuàng)建兩個(gè)線程來看看這種情況:

          public class ThreadSingleton extends Thread{
           @Override
           public void run() {
            try {
             System.out.println(Singleton.getInstance());
            } catch (Exception e) {
             e.printStackTrace();
            }
           }
           public static void main(String[] args) {
            ThreadSingleton s1 = new ThreadSingleton();
            s1.start(); //com.ys.pattern.Singleton@5994a1e9
            
            ThreadSingleton s2 = new ThreadSingleton();
            s2.start(); //com.ys.pattern.Singleton@40dea6bc
           }
          }

          很明顯:最后輸出結(jié)果的兩個(gè)實(shí)例是不同的。這便是線程安全問題。那么怎么解決這個(gè)問題呢?

          參考這篇博客:Java多線程同步:http://www.cnblogs.com/ysocean/p/6883729.html

          3.3 單例模式之懶漢模式(線程安全)

          這里我們采用同步代碼塊來達(dá)到線程安全

          //懶漢模式線程安全
          public class Singleton {
           //構(gòu)造器私有化
           private Singleton(){
            
           }
           //在類的內(nèi)部自己創(chuàng)建實(shí)例的引用
           private static Singleton singleton = null;

           //提供get 方法以供外界獲取單例
           public static Singleton getInstance() throws Exception{
            synchronized (Singleton.class{
             if(singleton == null){
              singleton = new Singleton();
             }
            }
            return singleton;
           }
           
          }

          我們給 getInstance() 方法創(chuàng)建實(shí)例時(shí)加了一把鎖 synchronzed,這樣會(huì)導(dǎo)致這個(gè)方法的并發(fā)為1,相當(dāng)于串行操作,如果這個(gè)單例在實(shí)際項(xiàng)目中會(huì)頻繁被調(diào)用,那就會(huì)頻繁加鎖,釋放鎖,會(huì)有性能瓶頸,不推薦此種方式。

          3.4 單例模式之懶漢模式(線程安全)--雙重校驗(yàn)鎖

          分析:上面的例子我們可以看到,synchronized 其實(shí)將方法內(nèi)部的所有語(yǔ)句都已經(jīng)包括了,每一個(gè)進(jìn)來的線程都要單獨(dú)進(jìn)入同步代碼塊,判斷實(shí)例是否存在,這就造成了性能的浪費(fèi)。那么我們可以想到,其實(shí)在第一次已經(jīng)創(chuàng)建了實(shí)例的情況下,后面再獲取實(shí)例的時(shí)候,可不可以不進(jìn)入這個(gè)同步代碼塊?

          //懶漢模式線程安全--雙重鎖校驗(yàn)
          public class Singleton {
           //構(gòu)造器私有化
           private Singleton(){
            
           }
           //在類的內(nèi)部自己創(chuàng)建實(shí)例的引用
           private static Singleton singleton = null;

           //提供get 方法以供外界獲取單例
           public static Singleton getInstance() throws Exception{
            if(singleton == null){
             synchronized (Singleton.class{
              if(singleton == null){
               singleton = new Singleton();
              }
             }
            }
            return singleton;
           }
           
          }

          以上的真的完美解決了單例模式嗎?其實(shí)并沒有,請(qǐng)看下面:

          3.5 單例模式之最終版

          我們知道編譯就是將源代碼翻譯成機(jī)械碼的過程,而Java虛擬機(jī)的目標(biāo)代碼不是本地機(jī)器碼,而是虛擬機(jī)代碼。編譯原理里面有個(gè)過程是編譯優(yōu)化,就是指在不改變?cè)瓉碚Z(yǔ)義的情況下,通過調(diào)整語(yǔ)句的順序,來讓程序運(yùn)行的更快,這個(gè)過程稱為 reorder。

          JVM 只是一個(gè)標(biāo)準(zhǔn),它并沒有規(guī)定有關(guān)編譯器優(yōu)化的內(nèi)容,也就是說,JVM可以自由的實(shí)現(xiàn)編譯器優(yōu)化。

          那么我們來再來考慮一下,創(chuàng)建一個(gè)變量需要哪些步驟?

          ①、申請(qǐng)一塊內(nèi)存,調(diào)用構(gòu)造方法進(jìn)行初始化

          ②、分配一個(gè)指針指向該內(nèi)存

          而這兩步誰先誰后呢?也就是存在這樣一種情況:先開辟一塊內(nèi)存,然后分配一個(gè)指針指向該內(nèi)存,最后調(diào)用構(gòu)造方法進(jìn)行初始化。

          那么針對(duì)單例模式的設(shè)計(jì),就會(huì)存在這樣一個(gè)問題:線程 A 開始創(chuàng)建 Singleton 的實(shí)例,此時(shí)線程 B已經(jīng)調(diào)用了 getInstance的()方法,首先判斷 instance 是否為 null。而我們上面說的那種模型, A 已經(jīng)把 instance 指向了那塊內(nèi)存,只是還沒來得及調(diào)用構(gòu)造方法進(jìn)行初始化,因此 B 檢測(cè)到 instance 不為 null,于是直接把  instance 返回了。那么問題出現(xiàn)了:盡管 instance 不為 null,但是 A 并沒有構(gòu)造完成,就像一套房子已經(jīng)給了你鑰匙,但是里面還沒有裝修,你并不能住進(jìn)去。

          解決方案:使用 volatile 關(guān)鍵字修飾 instance

          我們知道在當(dāng)前的Java內(nèi)存模型下,線程可以把變量保存在本地內(nèi)存(比如機(jī)器的寄存器)中,而不是直接在主存中進(jìn)行讀寫。這就可能造成一個(gè)線程在主存中修改了一個(gè)變量的值,而另外一個(gè)線程還繼續(xù)使用它在寄存器中的變量值的拷貝,造成數(shù)據(jù)的不一致。

          volatile修飾的成員變量在每次被線程訪問時(shí),都強(qiáng)迫從共享內(nèi)存中重讀該成員變量的值。而且,當(dāng)成員變量發(fā)生變化時(shí),強(qiáng)迫線程將變化值回寫到共享內(nèi)存。這樣在任何時(shí)刻,兩個(gè)不同的線程總是看到某個(gè)成員變量的同一個(gè)值。

          //懶漢模式線程安全--volatile
          public class Singleton {
              //構(gòu)造器私有化
              private Singleton(){

              }
              //在類的內(nèi)部自己創(chuàng)建實(shí)例的引用
              private static volatile Singleton singleton = null;

              //提供get 方法以供外界獲取單例
              public static Singleton getInstance() throws Exception{
                  if(singleton == null){
                      synchronized (Singleton.class{
                          if(singleton == null){
                              singleton = new Singleton();
                          }
                      }
                  }
                  return singleton;
              }

          }

          到此我們完美的解決了單例模式的問題。但是 volatile  關(guān)鍵字是 JDK1.5 才有的,也就是 JDK1.5 之前是不能這樣用的

          3.6 2021年4月補(bǔ)充

          上面我們說要加關(guān)鍵字 volatile ,禁止指令重排,防止單例對(duì)象new 出來后,并且賦值給 singleton,但是還沒來得及初始化這個(gè)問題。

          現(xiàn)在高版本的 Java(JDK9) 已經(jīng)在 JDK 內(nèi)部實(shí)現(xiàn)中解決了這個(gè)問題,把對(duì)象的 new 操作和初始化操作設(shè)計(jì)為 原子操作。

          相關(guān)參考鏈接:

          https://shipilev.net/blog/2014/safe-public-construction   https://chriswhocodes.com/vm-options-explorer.html

          3.7 單例模式之枚舉類

          public enum Singleton{
              INSTANCE;
              private Singleton(){}
          }

          通過Java枚舉類的自身特性,保證實(shí)例創(chuàng)建的線程安全和唯一性。

          3.8 單例模式之靜態(tài)內(nèi)部類

          public class InnerSingleton {
              private InnerSingleton() {
              }

              public static InnerSingleton getInstance() {
                  return Inner.instance;
              }

              static class Inner {
                  static InnerSingleton instance = new InnerSingleton();
              }

              public static void main(String[] args) {
                  System.out.println(InnerSingleton.getInstance() == InnerSingleton.getInstance());//true
                  System.out.println(InnerSingleton.getInstance().equals(InnerSingleton.getInstance()));//true

              }
          }

          4、單例模式的應(yīng)用

          說了那么多,那么單例模式在實(shí)際項(xiàng)目中有啥用呢?

          還是根據(jù)其核心概念,某個(gè)數(shù)據(jù)在系統(tǒng)中只能存在一份,就可以設(shè)計(jì)為單例。

          1、windows 系統(tǒng)的回收站,我們能在任何盤符刪除數(shù)據(jù),但是最后都是到了回收站中

          2、網(wǎng)站的計(jì)數(shù)器,不采用單例模式,很難實(shí)現(xiàn)同步

          3、數(shù)據(jù)庫(kù)連接池,可以節(jié)省打開或關(guān)閉數(shù)據(jù)庫(kù)連接所引起的效率損耗,用單例模式來維護(hù),可以大大降低這種損耗。當(dāng)然對(duì)于海量數(shù)據(jù)系統(tǒng),會(huì)存在多個(gè)數(shù)據(jù)庫(kù)連接池,比如一個(gè)能夠快速執(zhí)行SQL的連接池,還有一個(gè)是慢SQL,如果都放在一個(gè)池里面,會(huì)導(dǎo)致慢SQL執(zhí)行的時(shí)候,長(zhǎng)時(shí)間占用數(shù)據(jù)庫(kù)連接資源,導(dǎo)致其他SQL請(qǐng)求無法響應(yīng)。

          4、系統(tǒng)的配置信息類,通常只存在一個(gè)。


          瀏覽 148
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美AAA黄片 | 一级电影毛片 | 北条麻妃一区二区三区在线 | 2024国产精品自拍 | 日韩黄色免费网站 |