单列设计模式 懒汉式及多线程debug
生活随笔
收集整理的這篇文章主要介紹了
单列设计模式 懒汉式及多线程debug
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我們也是遵循演進的一個方式,一點點體會他們的不同,以及優缺點,單例模式也是創建型模式,我們在這個包下創建一個包,我們先學習一下懶漢式單例模式
package com.learn.design.pattern.creational.singleton;/*** * @author Leon.Sun**/
public class LazySingleton {/*** 首先我們聲明一個靜態的單例的一個對象* 懶漢式可以理解說他比較懶* 在初始化的時候呢* 是沒有創建的* 而是做一個延遲加載* 為了不讓外部來進行new* 這兩個點都比較好理解* * */private static LazySingleton lazySingleton = null;private LazySingleton(){if(lazySingleton != null){throw new RuntimeException("單例構造器禁止反射調用");}}/*** 我們寫一個獲取LazySingleton的一個方法* 他呢肯定是public的* getInstance* 這里面很簡單* * 第一種方式我們在getInstance方法上* 加上synchronized這個關鍵字* 是這個方法變成同步方法* 如果這個鎖加載靜態方法上* 相當于鎖的是LazySingleton這個類的clas文件* 如果這個不是靜態方法呢* 相當于鎖是在堆內存中生成的對象* 這里要注意一下* 也就是說在靜態方法中* 我們加了synchronized這個關鍵字* 來鎖這個方法的時候* 相當于鎖了這個類* 那我們換一種寫法* * 這個時候Thread0進入這個方法* 我們再切入到Thread1上* 可以看到斷點并沒有往下跳* 同時這個線程的狀態變成Monitor* 那我們可以認為Thread1現在是阻塞狀態* 這個是一個監視鎖* 我們可以看到下邊有一個提示* 這個提示很清晰* Thead1被線程0給阻塞了* 下面有一個藍色的按鈕* 如果點的話* 就會繼續線程0* 所以Thread1進不了getInstance的方法* 我們再切回Thread0* 單步走返回了* 然后準備輸出了* 我們再切回Thread1* 我們可以看到Thread1是Running狀態* F6單步* 這個時候我們看一下* 因為在if(lazySingleton == null)的時候* Thread0已經new完了* LazySingleton并不等于空* 他們兩返回的是同一個對象* 并且在LazySingleton里面* 這個時候只生產了一個實例* F8直接過* 看一下console* 這兩個拿到的也是同一個對象* 通過這種同步的方式* 我們解決了懶漢式在多線程可能引起的問題* 那我們也知道synchronized比較消耗資源* 這里面有一個加鎖和解鎖的一個開銷* 而且synchronized修飾static方法的時候* 鎖的是這個class* 這個鎖的范圍也是非常大的* 對性能也會有一定的影響* 那我們還有沒有一種方式繼續演進* 在性能和安全性方面取得平衡* 答案是有的* 那我們現在繼續演進我們的懶漢式* * * * @return*/public synchronized static LazySingleton getInstance(){
// public static LazySingleton getInstance(){/*** 剛剛寫靜態鎖的這種寫法和現在這種寫法是一樣的* synchronized (LazySingleton.class)這里鎖的也是LazySingleton* 那么再復原成同步方法* public synchronized static LazySingleton getInstance()* 就是這個樣子的* * */
// synchronized (LazySingleton.class) {/*** 做一個空判斷* 如果lazySingleton為null的話* 給他賦值* new一個LazySingleton* * 最開始的時候lazySingleton為null* 因為判斷lazySingleton == null結果為true* 進入到lazySingleton = new LazySingleton();* 進入之后還沒有執行new* 這個時候執行lazySingleton = new LazySingleton();* 這個單例就有值了* * 我們單步來到if(lazySingleton == null)* debug來調試多線程的節奏* 來觸發這種寫法在多線程中的問題* 那之前run的時候沒有發生這種問題呢* 那我們看到的是表象沒有發生* 但是實際有沒有發生其實是不確定的* 另外一種就是實際就沒有發生* 但是對于發生了但是我們沒有看到* 這種情況只要我們接著往下看* 肯定就理解了* 那另外一個他沒有發生* 這個呢也很好理解* 他就是沒有發生* 我說的發生是多線程的問題* 也就是這個單例模式是否只new出來一個對象* 那如果我們沒有用斷點干預* 直接run的話* 和CPU分配的時間有關* 是有一定概率的* 我們現在程序比較簡單* 如果我們程序復雜一些* 這種隱患還是有可能發生的* 既然有隱患我們就要消除掉他* 那現在我們繼續來看一下他* 把這個隱患找出來* 一定要掌握多線程debug* 講TOMCAT集群的時候呢* 操作了都進程debug* 現在在設計模式的課程中* 我們在寫多線程debug* 這個技能非常重要* 一定要學會* * 因為Thread0并沒有賦值上* 所以他還是為空* F6單步* 他也進來了* 那我再切回Thread0* * 我們切換到Thread0線程上* 為null進來* * 單步Thread1也進來了* 現在對于Thread1我直接讓他返回* 已經輸出了* 那我們再切回Thread0上* 這個時候Thread1是431* 但是lazySingleton = new LazySingleton()一旦完成* 那這個對象就變了* 已經變成了432* 這個時候他就返回* 然后輸出* 我們再看一下console* 那這個時候我們就可以看到* Thread1拿到的42D這個對象* Thread0是拿到420這個對象* 所以呢我們不能被表面所迷惑* 例如我們直接run的時候* 看到的對象是同一個* 但是在這中間獲取的可能不止一個對象* 所以呢這個是有一定概率的* 例如在lazySingleton里面* 如果第一個線程執行特別快* 先new上了* 那第二個線程判斷為null的時候* 就會返回false* 然后直接return* 所以呢* 具體這個返回值什么樣子* 都是有一定概率的* 那這個隱患我們是一定要消除的* 怎么消除呢* 很簡單* 對于懶漢式這種方式呢* 首先我們來到lazySingleton里邊* * */if(lazySingleton == null){/*** 第一個線程到這里的時候并且沒有執行這一行* 第二個線程達到if(lazySingleton == null)這里* 那if(lazySingleton == null)這一行判斷* 因為lazySingleton = new LazySingleton();這里還沒有new* 那么if(lazySingleton == null)判斷的結果是true* 所以第二個線程也會進入到lazySingleton = new LazySingleton();* 那這個對象就new了兩次* 同時會返回最后執行的lazySingleton* 那我們怎么驗證呢* 我們寫一個測試類* * 懶漢式注重的是延遲加載* * 這個時候lazySingleton還是null* 因為lazySingleton賦值new LazySingleton()* 這一行還沒有執行完* 所以他并沒有被復制上* 這個時候我們切到Thread1上* Thread1單步* * 可以看到在lazySingleton = new LazySingleton()還沒有執行的時候* lazySingleton是有值的* 但是我們馬上就要執行它了* F6單步* 現在可以看到lazySingleton值變了* 也就是說在我們寫的懶漢式的單例模式中* lazySingleton在多線程的模式中* 生成了不止一個實例* 那現在是兩個線程* 如果是多個線程呢* 所以在第一次初始化的時候* 有可能創建很多個的單例對象* 如果這個單例類的對象特別消耗資源* 那很有可能造成系統故障* 這個呢是非常有風險的* 那在我們這個例子中lazySingleton并沒有特別消耗資源的地方* 但是場景是一樣的* 那我們現在再切換到Thread0上* lazySingleton這個對象已經被Thread1重新賦值了* 那現在F8過* 主動跳到主線程* 我們看一下console* 這個時候我們可以看到* Thread0和Thread1拿到的還是同一個對象* * 現在Thread0在lazySingleton = new LazySingleton();這一行上* 我們再切換到Thread1上* * */lazySingleton = new LazySingleton();}
// }/*** 把這個對象返回* 把這個對象返回回去* 這種方式是線程不安全的* 我們看一看代碼* 在單線程的時候* 這種模式這種寫法* 是OK的* 但是一旦多線程來使用這個單例的話* 假設我們現在兩個線程* * 這個時候Thread0已經把lazySingleton賦值上了* 這個時候我們在切到Thread1上* * */return lazySingleton;}// public static void main(String[] args) throws Exception {
// Class objectClass = LazySingleton.class;
// Constructor c = objectClass.getDeclaredConstructor();
// c.setAccessible(true);
///*** 通過LazySingleton這個類調用getInstance方法* 因為它是private構造器* 所以在外部是new不到他的* 然后我們直接getInstance* * 這樣簡單一個單線程獲取的單例呢就完成了* 只有使用它的時候才初始化* 如果不使用就初始化LazySingleton對象* 那main本身是一個線程* 現在我們在這個線程中再創建兩個線程* 去獲取現在這種寫法的單例的時候* 會碰到什么問題呢* 我們一起來看一下* 首先我們寫一下線程的類* * * */
// LazySingleton o1 = LazySingleton.getInstance();
// System.out.println("Program end.....");
//
// Field flag = o1.getClass().getDeclaredField("flag");
// flag.setAccessible(true);
// flag.set(o1,true);
//
//
// LazySingleton o2 = (LazySingleton) c.newInstance();
//
// System.out.println(o1);
// System.out.println(o2);
// System.out.println(o1==o2);
// }}
package com.learn.design.pattern.creational.singleton;/*** * @author Leon.Sun**/
public class LazySingleton {/*** 首先我們聲明一個靜態的單例的一個對象* 懶漢式可以理解說他比較懶* 在初始化的時候呢* 是沒有創建的* 而是做一個延遲加載* 為了不讓外部來進行new* 這兩個點都比較好理解* * */private static LazySingleton lazySingleton = null;private LazySingleton(){if(lazySingleton != null){throw new RuntimeException("單例構造器禁止反射調用");}}/*** 我們寫一個獲取LazySingleton的一個方法* 他呢肯定是public的* getInstance* 這里面很簡單* * 第一種方式我們在getInstance方法上* 加上synchronized這個關鍵字* 是這個方法變成同步方法* 如果這個鎖加載靜態方法上* 相當于鎖的是LazySingleton這個類的clas文件* 如果這個不是靜態方法呢* 相當于鎖是在堆內存中生成的對象* 這里要注意一下* 也就是說在靜態方法中* 我們加了synchronized這個關鍵字* 來鎖這個方法的時候* 相當于鎖了這個類* 那我們換一種寫法* * 這個時候Thread0進入這個方法* 我們再切入到Thread1上* 可以看到斷點并沒有往下跳* 同時這個線程的狀態變成Monitor* 那我們可以認為Thread1現在是阻塞狀態* 這個是一個監視鎖* 我們可以看到下邊有一個提示* 這個提示很清晰* Thead1被線程0給阻塞了* 下面有一個藍色的按鈕* 如果點的話* 就會繼續線程0* 所以Thread1進不了getInstance的方法* 我們再切回Thread0* 單步走返回了* 然后準備輸出了* 我們再切回Thread1* 我們可以看到Thread1是Running狀態* F6單步* 這個時候我們看一下* 因為在if(lazySingleton == null)的時候* Thread0已經new完了* LazySingleton并不等于空* 他們兩返回的是同一個對象* 并且在LazySingleton里面* 這個時候只生產了一個實例* F8直接過* 看一下console* 這兩個拿到的也是同一個對象* 通過這種同步的方式* 我們解決了懶漢式在多線程可能引起的問題* 那我們也知道synchronized比較消耗資源* 這里面有一個加鎖和解鎖的一個開銷* 而且synchronized修飾static方法的時候* 鎖的是這個class* 這個鎖的范圍也是非常大的* 對性能也會有一定的影響* 那我們還有沒有一種方式繼續演進* 在性能和安全性方面取得平衡* 答案是有的* 那我們現在繼續演進我們的懶漢式* * * * @return*/public synchronized static LazySingleton getInstance(){
// public static LazySingleton getInstance(){/*** 剛剛寫靜態鎖的這種寫法和現在這種寫法是一樣的* synchronized (LazySingleton.class)這里鎖的也是LazySingleton* 那么再復原成同步方法* public synchronized static LazySingleton getInstance()* 就是這個樣子的* * */
// synchronized (LazySingleton.class) {/*** 做一個空判斷* 如果lazySingleton為null的話* 給他賦值* new一個LazySingleton* * 最開始的時候lazySingleton為null* 因為判斷lazySingleton == null結果為true* 進入到lazySingleton = new LazySingleton();* 進入之后還沒有執行new* 這個時候執行lazySingleton = new LazySingleton();* 這個單例就有值了* * 我們單步來到if(lazySingleton == null)* debug來調試多線程的節奏* 來觸發這種寫法在多線程中的問題* 那之前run的時候沒有發生這種問題呢* 那我們看到的是表象沒有發生* 但是實際有沒有發生其實是不確定的* 另外一種就是實際就沒有發生* 但是對于發生了但是我們沒有看到* 這種情況只要我們接著往下看* 肯定就理解了* 那另外一個他沒有發生* 這個呢也很好理解* 他就是沒有發生* 我說的發生是多線程的問題* 也就是這個單例模式是否只new出來一個對象* 那如果我們沒有用斷點干預* 直接run的話* 和CPU分配的時間有關* 是有一定概率的* 我們現在程序比較簡單* 如果我們程序復雜一些* 這種隱患還是有可能發生的* 既然有隱患我們就要消除掉他* 那現在我們繼續來看一下他* 把這個隱患找出來* 一定要掌握多線程debug* 講TOMCAT集群的時候呢* 操作了都進程debug* 現在在設計模式的課程中* 我們在寫多線程debug* 這個技能非常重要* 一定要學會* * 因為Thread0并沒有賦值上* 所以他還是為空* F6單步* 他也進來了* 那我再切回Thread0* * 我們切換到Thread0線程上* 為null進來* * 單步Thread1也進來了* 現在對于Thread1我直接讓他返回* 已經輸出了* 那我們再切回Thread0上* 這個時候Thread1是431* 但是lazySingleton = new LazySingleton()一旦完成* 那這個對象就變了* 已經變成了432* 這個時候他就返回* 然后輸出* 我們再看一下console* 那這個時候我們就可以看到* Thread1拿到的42D這個對象* Thread0是拿到420這個對象* 所以呢我們不能被表面所迷惑* 例如我們直接run的時候* 看到的對象是同一個* 但是在這中間獲取的可能不止一個對象* 所以呢這個是有一定概率的* 例如在lazySingleton里面* 如果第一個線程執行特別快* 先new上了* 那第二個線程判斷為null的時候* 就會返回false* 然后直接return* 所以呢* 具體這個返回值什么樣子* 都是有一定概率的* 那這個隱患我們是一定要消除的* 怎么消除呢* 很簡單* 對于懶漢式這種方式呢* 首先我們來到lazySingleton里邊* * */if(lazySingleton == null){/*** 第一個線程到這里的時候并且沒有執行這一行* 第二個線程達到if(lazySingleton == null)這里* 那if(lazySingleton == null)這一行判斷* 因為lazySingleton = new LazySingleton();這里還沒有new* 那么if(lazySingleton == null)判斷的結果是true* 所以第二個線程也會進入到lazySingleton = new LazySingleton();* 那這個對象就new了兩次* 同時會返回最后執行的lazySingleton* 那我們怎么驗證呢* 我們寫一個測試類* * 懶漢式注重的是延遲加載* * 這個時候lazySingleton還是null* 因為lazySingleton賦值new LazySingleton()* 這一行還沒有執行完* 所以他并沒有被復制上* 這個時候我們切到Thread1上* Thread1單步* * 可以看到在lazySingleton = new LazySingleton()還沒有執行的時候* lazySingleton是有值的* 但是我們馬上就要執行它了* F6單步* 現在可以看到lazySingleton值變了* 也就是說在我們寫的懶漢式的單例模式中* lazySingleton在多線程的模式中* 生成了不止一個實例* 那現在是兩個線程* 如果是多個線程呢* 所以在第一次初始化的時候* 有可能創建很多個的單例對象* 如果這個單例類的對象特別消耗資源* 那很有可能造成系統故障* 這個呢是非常有風險的* 那在我們這個例子中lazySingleton并沒有特別消耗資源的地方* 但是場景是一樣的* 那我們現在再切換到Thread0上* lazySingleton這個對象已經被Thread1重新賦值了* 那現在F8過* 主動跳到主線程* 我們看一下console* 這個時候我們可以看到* Thread0和Thread1拿到的還是同一個對象* * 現在Thread0在lazySingleton = new LazySingleton();這一行上* 我們再切換到Thread1上* * */lazySingleton = new LazySingleton();}
// }/*** 把這個對象返回* 把這個對象返回回去* 這種方式是線程不安全的* 我們看一看代碼* 在單線程的時候* 這種模式這種寫法* 是OK的* 但是一旦多線程來使用這個單例的話* 假設我們現在兩個線程* * 這個時候Thread0已經把lazySingleton賦值上了* 這個時候我們在切到Thread1上* * */return lazySingleton;}// public static void main(String[] args) throws Exception {
// Class objectClass = LazySingleton.class;
// Constructor c = objectClass.getDeclaredConstructor();
// c.setAccessible(true);
///*** 通過LazySingleton這個類調用getInstance方法* 因為它是private構造器* 所以在外部是new不到他的* 然后我們直接getInstance* * 這樣簡單一個單線程獲取的單例呢就完成了* 只有使用它的時候才初始化* 如果不使用就初始化LazySingleton對象* 那main本身是一個線程* 現在我們在這個線程中再創建兩個線程* 去獲取現在這種寫法的單例的時候* 會碰到什么問題呢* 我們一起來看一下* 首先我們寫一下線程的類* * * */
// LazySingleton o1 = LazySingleton.getInstance();
// System.out.println("Program end.....");
//
// Field flag = o1.getClass().getDeclaredField("flag");
// flag.setAccessible(true);
// flag.set(o1,true);
//
//
// LazySingleton o2 = (LazySingleton) c.newInstance();
//
// System.out.println(o1);
// System.out.println(o2);
// System.out.println(o1==o2);
// }}
package com.learn.design.pattern.creational.singleton;import java.io.IOException;
import java.lang.reflect.InvocationTargetException;/*** 本身Main是一個主線程* 從上至下而執行* 那執行到t1.start();的時候呢* 就開啟了兩個線程* 一會我們在執行的時候其實是三個線程在執行* 主線程還有t1和t2* 那斷點呢我們也打上了* 現在運行debug* * 但是剛剛通過debug我們已經知道了* 他們返回同一個對象* 是因為最后的線程重新賦值了* 并且在重新賦值之后* 兩個線程才進行return的* 所以我們在console里面看到的是同一個對象* 把我們再debug操作一下* 讓他們返回不同的對象* * * @author Leon.Sun**/
public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazySingleton lazySingleton = LazySingleton.getInstance();// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());/*** new一個t1線程* */Thread t1 = new Thread(new T());/*** 再new一個t2線程* */Thread t2 = new Thread(new T());/*** 我們可以看到Thread1和Thread0拿到的是同一個對象* 那這個呢是run的情況下* 如果我們debug進行干預的話* * */t1.start();t2.start();/*** 現在這個線程是主線程的* 我們關心的是Main Thread0 Thread1* 那現在這個三個線程的狀態都是Running* 那我們現在通過Frame切換到Thread0上* 可以看到Thread0調用getInstance方法了* * */System.out.println("program end");// HungrySingleton instance = HungrySingleton.getInstance();
// EnumInstance instance = EnumInstance.getInstance();
// instance.setData(new Object());
//
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
// oos.writeObject(instance);
//
// File file = new File("singleton_file");
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
//HungrySingleton newInstance = (HungrySingleton) ois.readObject();
// EnumInstance newInstance = (EnumInstance) ois.readObject();
//
// System.out.println(instance.getData());
// System.out.println(newInstance.getData());
// System.out.println(instance.getData() == newInstance.getData());// Class objectClass = HungrySingleton.class;
// Class objectClass = StaticInnerClassSingleton.class;// Class objectClass = LazySingleton.class;
// Class objectClass = EnumInstance.class;// Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
//
// constructor.setAccessible(true);
// EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);//
// LazySingleton newInstance = (LazySingleton) constructor.newInstance();
// LazySingleton instance = LazySingleton.getInstance();// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
// StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();// HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
// HungrySingleton instance = HungrySingleton.getInstance();// System.out.println(instance);
// System.out.println(newInstance);
// System.out.println(instance == newInstance);// EnumInstance instance = EnumInstance.getInstance();
// instance.printTest();}
}
?
總結
以上是生活随笔為你收集整理的单列设计模式 懒汉式及多线程debug的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单例模式讲解
- 下一篇: asp.net ajax控件工具集 Au