国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

高并發(fā)下線程安全的單例模式

2019-11-08 02:47:53
字體:
供稿:網(wǎng)友

在所有的設(shè)計模式中,單例模式是我們在項目開發(fā)中最為常見的設(shè)計模式之一,而單例模式有很多種實現(xiàn)方式,你是否都了解呢?高并發(fā)下如何保證單例模式的線程安全性呢?如何保證序列化后的單例對象在反序列化后任然是單例的呢?這些問題在看了本文之后都會一一的告訴你答案,趕快來閱讀吧!

什么是單例模式?

在文章開始之前我們還是有必要介紹一下什么是單例模式。單例模式是為確保一個類只有一個實例,并為整個系統(tǒng)提供一個全局訪問點的一種模式方法。

從概念中體現(xiàn)出了單例的一些特點:

(1)、在任何情況下,單例類永遠(yuǎn)只有一個實例存在

(2)、單例需要有能力為整個系統(tǒng)提供這一唯一實例  

為了便于讀者更好的理解這些概念,下面給出這么一段內(nèi)容敘述:

在計算機(jī)系統(tǒng)中,線程池、緩存、日志對象、對話框、打印機(jī)、顯卡的驅(qū)動程序?qū)ο蟪1辉O(shè)計成單例。這些應(yīng)用都或多或少具有資源管理器的功能。每臺計算機(jī)可以有若干個打印機(jī),但只能有一個PRinter Spooler,以避免兩個打印作業(yè)同時輸出到打印機(jī)中。每臺計算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調(diào)用。總之,選擇單例模式就是為了避免不一致狀態(tài),避免政出多頭。正是由于這個特點,單例對象通常作為程序中的存放配置信息的載體,因為它能保證其他對象讀到一致的信息。例如在某個服務(wù)器程序中,該服務(wù)器的配置信息可能存放在數(shù)據(jù)庫或文件中,這些配置數(shù)據(jù)由某個單例對象統(tǒng)一讀取,服務(wù)進(jìn)程中的其他對象如果要獲取這些配置信息,只需訪問該單例對象即可。這種方式極大地簡化了在復(fù)雜環(huán)境 下,尤其是多線程環(huán)境下的配置管理,但是隨著應(yīng)用場景的不同,也可能帶來一些同步問題。    

各式各樣的單例實現(xiàn)

溫馨提示:本文敘述中涉及到的相關(guān)源碼可以在這里進(jìn)行下載源碼,讀者可免積分下載。

1、餓漢式單例

餓漢式單例是指在方法調(diào)用前,實例就已經(jīng)創(chuàng)建好了。下面是實現(xiàn)代碼:

[java] view plain copy print?在CODE上查看代碼片package org.mlinge.s01;    public class MySingleton {            private static MySingleton instance = new MySingleton();            private MySingleton(){}            public static MySingleton getInstance() {          return instance;      }        }  以上是單例的餓漢式實現(xiàn),我們來看看餓漢式在多線程下的執(zhí)行情況,給出一段多線程的執(zhí)行代碼:

[java] view%20plain copy print?package org.mlinge.s01;    public class MyThread extends Thread{            @Override      public void run() {           System.out.println(MySingleton.getInstance().hashCode());      }            public static void main(String[] args) {                     MyThread[] mts = new MyThread[10];          for(int i = 0 ; i < mts.length ; i++){              mts[i] = new MyThread();          }                    for (int j = 0; j < mts.length; j++) {              mts[j].start();          }      }  }  以上代碼運行結(jié)果:

[plain] view%20plain copy print?1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  從運行結(jié)果可以看出實例變量額hashCode值一致,這說明對象是同一個,餓漢式單例實現(xiàn)了。

2、懶漢式單例懶漢式單例是指在方法調(diào)用獲取實例時才創(chuàng)建實例,因為相對餓漢式顯得“不急迫”,所以被叫做“懶漢模式”。下面是實現(xiàn)代碼:

[java] view%20plain copy print?package org.mlinge.s02;    public class MySingleton {            private static MySingleton instance = null;            private MySingleton(){}            public static MySingleton getInstance() {          if(instance == null){//懶漢式              instance = new MySingleton();          }          return instance;      }  }  這里實現(xiàn)了懶漢式的單例,但是熟悉多線程并發(fā)編程的朋友應(yīng)該可以看出,在多線程并發(fā)下這樣的實現(xiàn)是無法保證實例實例唯一的,甚至可以說這樣的失效是完全錯誤的,下面我們就來看一下多線程并發(fā)下的執(zhí)行情況,這里為了看到效果,我們對上面的代碼做一小點修改:

[java] view%20plain copy print?package org.mlinge.s02;    public class MySingleton {            private static MySingleton instance = null;            private MySingleton(){}            public static MySingleton getInstance() {          try {               if(instance != null){//懶漢式                                 }else{                  //創(chuàng)建實例之前可能會有一些準(zhǔn)備性的耗時工作                   Thread.sleep(300);                  instance = new MySingleton();              }          } catch (InterruptedException e) {               e.printStackTrace();          }          return instance;      }  }  這里假設(shè)在創(chuàng)建實例前有一些準(zhǔn)備性的耗時工作要處理,多線程調(diào)用:

[java] view%20plain copy print?package org.mlinge.s02;    public class MyThread extends Thread{            @Override      public void run() {           System.out.println(MySingleton.getInstance().hashCode());      }            public static void main(String[] args) {                     MyThread[] mts = new MyThread[10];          for(int i = 0 ; i < mts.length ; i++){              mts[i] = new MyThread();          }                    for (int j = 0; j < mts.length; j++) {              mts[j].start();          }      }  }  執(zhí)行結(jié)果如下:

[plain] view%20plain copy print?1210420568  1210420568  1935123450  1718900954  1481297610  1863264879  369539795  1210420568  1210420568  602269801  從這里執(zhí)行結(jié)果可以看出,單例的線程安全性并沒有得到保證,那要怎么解決呢?

3、線程安全的懶漢式單例要保證線程安全,我們就得需要使用同步鎖機(jī)制,下面就來看看我們?nèi)绾我徊讲降慕鉀Q%20存在線程安全問題的懶漢式單例(錯誤的單例)。

(1)、%20方法中聲明synchronized關(guān)鍵字出現(xiàn)非線程安全問題,是由于多個線程可以同時進(jìn)入getInstance()方法,那么只需要對該方法進(jìn)行synchronized的鎖同步即可:

[java] view%20plain copy print?package org.mlinge.s03;    public class MySingleton {            private static MySingleton instance = null;            private MySingleton(){}            public synchronized static MySingleton getInstance() {          try {               if(instance != null){//懶漢式                                 }else{                  //創(chuàng)建實例之前可能會有一些準(zhǔn)備性的耗時工作                   Thread.sleep(300);                  instance = new MySingleton();              }          } catch (InterruptedException e) {               e.printStackTrace();          }          return instance;      }  }  此時任然使用前面驗證多線程下執(zhí)行情況的MyThread類來進(jìn)行驗證,將其放入到org.mlinge.s03包下運行,執(zhí)行結(jié)果如下:

[plain] view%20plain copy print?1689058373  1689058373  1689058373  1689058373  1689058373  1689058373  1689058373  1689058373  1689058373  1689058373  

從執(zhí)行結(jié)果上來看,問題已經(jīng)解決了,但是這種實現(xiàn)方式的運行效率會很低。同步方法效率低,那我們考慮使用同步代碼塊來實現(xiàn):

(2)、%20同步代碼塊實現(xiàn)

[java] view%20plain copy print?package org.mlinge.s03;    public class MySingleton {            private static MySingleton instance = null;            private MySingleton(){}            //public synchronized static MySingleton getInstance() {      public static MySingleton getInstance() {          try {               synchronized (MySingleton.class) {                  if(instance != null){//懶漢式                                         }else{                      //創(chuàng)建實例之前可能會有一些準(zhǔn)備性的耗時工作                       Thread.sleep(300);                      instance = new MySingleton();                  }              }          } catch (InterruptedException e) {               e.printStackTrace();          }          return instance;      }  }  這里的實現(xiàn)能夠保證多線程并發(fā)下的線程安全性,但是這樣的實現(xiàn)將全部的代碼都被鎖上了,同樣的效率很低下。

(3)、%20針對某些重要的代碼來進(jìn)行單獨的同步(可能非線程安全)針對某些重要的代碼進(jìn)行單獨的同步,而不是全部進(jìn)行同步,可以極大的提高執(zhí)行效率,我們來看一下:

[java] view%20plain copy print?package org.mlinge.s04;    public class MySingleton {            private static MySingleton instance = null;            private MySingleton(){}             public static MySingleton getInstance() {          try {                if(instance != null){//懶漢式                                 }else{                  //創(chuàng)建實例之前可能會有一些準(zhǔn)備性的耗時工作                   Thread.sleep(300);                  synchronized (MySingleton.class) {                      instance = new MySingleton();                  }              }           } catch (InterruptedException e) {               e.printStackTrace();          }          return instance;      }  }  此時同樣使用前面驗證多線程下執(zhí)行情況的MyThread類來進(jìn)行驗證,將其放入到org.mlinge.s04包下運行,執(zhí)行結(jié)果如下:[plain] view%20plain copy print?1481297610  397630378  1863264879  1210420568  1935123450  369539795  590202901  1718900954  1689058373  602269801  從運行結(jié)果來看,這樣的方法進(jìn)行代碼塊同步,代碼的運行效率是能夠得到提升,但是卻沒能保住線程的安全性。看來還得進(jìn)一步考慮如何解決此問題。

(4)、 Double%20Check%20Locking 雙檢查鎖機(jī)制(推薦)為了達(dá)到線程安全,又能提高代碼執(zhí)行效率,我們這里可以采用DCL的雙檢查鎖機(jī)制來完成,代碼實現(xiàn)如下:

[java] view%20plain copy print?package org.mlinge.s05;    public class MySingleton {            //使用volatile關(guān)鍵字保其可見性      volatile private static MySingleton instance = null;            private MySingleton(){}             public static MySingleton getInstance() {          try {                if(instance != null){//懶漢式                                 }else{                  //創(chuàng)建實例之前可能會有一些準(zhǔn)備性的耗時工作                   Thread.sleep(300);                  synchronized (MySingleton.class) {                      if(instance == null){//二次檢查                          instance = new MySingleton();                      }                  }              }           } catch (InterruptedException e) {               e.printStackTrace();          }          return instance;      }  }  將前面驗證多線程下執(zhí)行情況的MyThread類放入到org.mlinge.s05包下運行,執(zhí)行結(jié)果如下:[java] view%20plain copy print?369539795  369539795  369539795  369539795  369539795  369539795  369539795  369539795  369539795  369539795  從運行結(jié)果來看,該中方法保證了多線程并發(fā)下的線程安全性。

這里在聲明變量時使用了volatile關(guān)鍵字來保證其線程間的可見性;在同步代碼塊中使用二次檢查,以保證其不被重復(fù)實例化。集合其二者,這種實現(xiàn)方式既保證了其高效性,也保證了其線程安全性。

4、使用靜態(tài)內(nèi)置類實現(xiàn)單例模式DCL解決了多線程并發(fā)下的線程安全問題,其實使用其他方式也可以達(dá)到同樣的效果,代碼實現(xiàn)如下:

[java] view%20plain copy print?package org.mlinge.s06;    public class MySingleton {            //內(nèi)部類      private static class MySingletonHandler{          private static MySingleton instance = new MySingleton();      }             private MySingleton(){}             public static MySingleton getInstance() {           return MySingletonHandler.instance;      }  }  以上代碼就是使用靜態(tài)內(nèi)置類實現(xiàn)了單例模式,這里將前面驗證多線程下執(zhí)行情況的MyThread類放入到org.mlinge.s06包下運行,執(zhí)行結(jié)果如下:[java] view%20plain copy print?1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  從運行結(jié)果來看,靜態(tài)內(nèi)部類實現(xiàn)的單例在多線程并發(fā)下單個實例得到了保證。

5、序列化與反序列化的單例模式實現(xiàn)靜態(tài)內(nèi)部類雖然保證了單例在多線程并發(fā)下的線程安全性,但是在遇到序列化對象時,默認(rèn)的方式運行得到的結(jié)果就是多例的。

代碼實現(xiàn)如下:

[java] view%20plain copy print?package org.mlinge.s07;    import java.io.Serializable;    public class MySingleton implements Serializable {             private static final long serialVersionUID = 1L;        //內(nèi)部類      private static class MySingletonHandler{          private static MySingleton instance = new MySingleton();      }             private MySingleton(){}             public static MySingleton getInstance() {           return MySingletonHandler.instance;      }  }  序列化與反序列化測試代碼:

[java] view%20plain copy print?package org.mlinge.s07;    import java.io.File;  import java.io.FileInputStream;  import java.io.FileNotFoundException;  import java.io.FileOutputStream;  import java.io.IOException;  import java.io.ObjectInputStream;  import java.io.ObjectOutputStream;    public class SaveAndReadForSingleton {            public static void main(String[] args) {          MySingleton singleton = MySingleton.getInstance();                    File file = new File("MySingleton.txt");                    try {              FileOutputStream fos = new FileOutputStream(file);              ObjectOutputStream oos = new ObjectOutputStream(fos);              oos.writeObject(singleton);              fos.close();              oos.close();              System.out.println(singleton.hashCode());          } catch (FileNotFoundException e) {               e.printStackTrace();          } catch (IOException e) {               e.printStackTrace();          }                    try {              FileInputStream fis = new FileInputStream(file);              ObjectInputStream ois = new ObjectInputStream(fis);              MySingleton rSingleton = (MySingleton) ois.readObject();              fis.close();              ois.close();              System.out.println(rSingleton.hashCode());          } catch (FileNotFoundException e) {               e.printStackTrace();          } catch (IOException e) {               e.printStackTrace();          } catch (ClassNotFoundException e) {               e.printStackTrace();          }                }  }  運行以上代碼,得到的結(jié)果如下:

[sql] view%20plain copy print?865113938  1442407170  從結(jié)果中我們發(fā)現(xiàn),序列號對象的hashCode和反序列化后得到的對象的hashCode值不一樣,說明反序列化后返回的對象是重新實例化的,單例被破壞了。那怎么來解決這一問題呢?

解決辦法就是在反序列化的過程中使用readResolve()方法,單例實現(xiàn)的代碼如下:

[java] view%20plain copy print?package org.mlinge.s07;    import java.io.ObjectStreamException;  import java.io.Serializable;    public class MySingleton implements Serializable {             private static final long serialVersionUID = 1L;        //內(nèi)部類      private static class MySingletonHandler{          private static MySingleton instance = new MySingleton();      }             private MySingleton(){}             public static MySingleton getInstance() {           return MySingletonHandler.instance;      }            //該方法在反序列化時會被調(diào)用,該方法不是接口定義的方法,有點兒約定俗成的感覺      protected Object readResolve() throws ObjectStreamException {          System.out.println("調(diào)用了readResolve方法!");          return MySingletonHandler.instance;       }  }  再次運行上面的測試代碼,得到的結(jié)果如下:

[plain] view%20plain copy print?865113938  調(diào)用了readResolve方法!  865113938  從運行結(jié)果可知,添加readResolve方法后反序列化后得到的實例和序列化前的是同一個實例,單個實例得到了保證。

6、使用static代碼塊實現(xiàn)單例靜態(tài)代碼塊中的代碼在使用類的時候就已經(jīng)執(zhí)行了,所以可以應(yīng)用靜態(tài)代碼塊的這個特性的實現(xiàn)單例設(shè)計模式。

[java] view%20plain copy print?package org.mlinge.s08;    public class MySingleton{             private static MySingleton instance = null;             private MySingleton(){}        static{          instance = new MySingleton();      }            public static MySingleton getInstance() {           return instance;      }   }  測試代碼如下:

[java] view%20plain copy print?package org.mlinge.s08;    public class MyThread extends Thread{            @Override      public void run() {           for (int i = 0; i < 5; i++) {              System.out.println(MySingleton.getInstance().hashCode());          }      }            public static void main(String[] args) {                     MyThread[] mts = new MyThread[3];          for(int i = 0 ; i < mts.length ; i++){              mts[i] = new MyThread();          }                    for (int j = 0; j < mts.length; j++) {              mts[j].start();          }      }  }  運行結(jié)果如下:

[java] view%20plain copy print?1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  1718900954  從運行結(jié)果看,單例的線程安全性得到了保證。

7、使用枚舉數(shù)據(jù)類型實現(xiàn)單例模式枚舉enum和靜態(tài)代碼塊的特性相似,在使用枚舉時,構(gòu)造方法會被自動調(diào)用,利用這一特性也可以實現(xiàn)單例:

[java] view%20plain copy print?package org.mlinge.s09;    public enum EnumFactory{             singletonFactory;            private MySingleton instance;            private EnumFactory(){//枚舉類的構(gòu)造方法在類加載是被實例化          instance = new MySingleton();      }                public MySingleton getInstance(){          return instance;      }        }    class MySingleton{//需要獲實現(xiàn)單例的類,比如數(shù)據(jù)庫連接Connection      public MySingleton(){}   }  測試代碼如下:

[java] view%20plain copy print?package org.mlinge.s09;    public class MyThread extends Thread{            @Override      public void run() {           System.out.println(EnumFactory.singletonFactory.getInstance().hashCode());      }            public static void main(String[] args) {                     MyThread[] mts = new MyThread[10];          for(int i = 0 ; i < mts.length ; i++){              mts[i] = new MyThread();          }                    for (int j = 0; j < mts.length; j++) {              mts[j].start();          }      }  }  執(zhí)行后得到的結(jié)果:[java] view%20plain copy print?1481297610  1481297610  1481297610  1481297610  1481297610  1481297610  1481297610  1481297610  1481297610  1481297610  運行結(jié)果表明單例得到了保證,但是這樣寫枚舉類被完全暴露了,據(jù)說違反了“職責(zé)單一原則”,那我們來看看怎么進(jìn)行改造呢。

8、完善使用enum枚舉實現(xiàn)單例模式不暴露枚舉類實現(xiàn)細(xì)節(jié)的封裝代碼如下:

[java] view%20plain copy print?package org.mlinge.s10;    public class ClassFactory{             private enum MyEnumSingleton{          singletonFactory;                    private MySingleton instance;                    private MyEnumSingleton(){//枚舉類的構(gòu)造方法在類加載是被實例化              instance = new MySingleton();          }             public MySingleton getInstance(){              return instance;          }      }          public static MySingleton getInstance(){          return MyEnumSingleton.singletonFactory.getInstance();      }  }    class MySingleton{//需要獲實現(xiàn)單例的類,比如數(shù)據(jù)庫連接Connection      public MySingleton(){}   }  驗證單例實現(xiàn)的代碼如下:

[java] view%20plain copy print?package org.mlinge.s10;    public class MyThread extends Thread{            @Override      public void run() {           System.out.println(ClassFactory.getInstance().hashCode());      }            public static void main(String[] args) {                     MyThread[] mts = new MyThread[10];          for(int i = 0 ; i < mts.length ; i++){              mts[i] = new MyThread();          }                    for (int j = 0; j < mts.length; j++) {              mts[j].start();          }      }  }  驗證結(jié)果:

[java] view%20plain copy print?派生到我的代碼片1935123450  1935123450  1935123450  1935123450  1935123450  1935123450  1935123450  1935123450  1935123450  1935123450  驗證結(jié)果表明,完善后的單例實現(xiàn)更為合理。

以上就是本文要介紹的所有單例模式的實現(xiàn),相信認(rèn)真閱讀的讀者都已經(jīng)明白文章開頭所引入的那幾個問題了,祝大家讀得開心:-D!

原文地址:http://blog.csdn.net/cselmu9/article/details/51366946#t11


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 榕江县| 洪雅县| 玉树县| 神农架林区| 丽水市| 台州市| 宕昌县| 敦化市| 环江| 汽车| 林芝县| 拉萨市| 华蓥市| 梅河口市| 晋中市| 阿克苏市| 洮南市| 赫章县| 徐汇区| 宁城县| 利川市| 山东省| 崇州市| 黎平县| 永仁县| 科尔| 印江| 达拉特旗| 通城县| 德昌县| 内乡县| 万安县| 河池市| 贵定县| 泗阳县| 漯河市| 正定县| 蒙城县| 高淳县| 连云港市| 民权县|