1. 程式人生 > 實用技巧 >上機考試

上機考試

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。

這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。

注意:

  • 1、單例類只能有一個例項。
  • 2、單例類必須自己建立自己的唯一例項。
  • 3、單例類必須給所有其他物件提供這一例項。

上面這幾句引自https://www.runoob.com/design-pattern/singleton-pattern.html

眾所周知,單例模式分為懶漢式和餓漢式。下面我逐一介紹。

餓漢式:

public class HungryMan {

    private HungryMan(){}

    private static final HungryMan HUNGRY_MAN = new HungryMan();

    public static HungryMan getInstance(){
        return HUNGRY_MAN;
    }

    public static void main(String[] args) {
        HungryMan hungryMan1 = HungryMan.getInstance();
        HungryMan hungryMan2 
= HungryMan.getInstance(); System.out.println(hungryMan1.hashCode()); System.out.println(hungryMan2.hashCode()); } }

說明建立的是同一個物件

但是餓漢式會造成資源的浪費

懶漢式:

public class LazyMan {

    private LazyMan(){}

    public static LazyMan lazyMan;

    public static LazyMan getInstance(){
        
if(lazyMan == null){ lazyMan = new LazyMan(); return lazyMan; } return lazyMan; } public static void main(String[] args) { LazyMan lazyMan1 = LazyMan.getInstance(); LazyMan lazyMan2 = LazyMan.getInstance(); System.out.println(lazyMan1.hashCode()); System.out.println(lazyMan2.hashCode()); } }

看到兩個物件是一樣的

但是多執行緒的情況下懶漢式是不安全的

檢驗不安全:

public class LazyMan {

    private LazyMan(){}

    public static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(lazyMan == null){
            //睡一會
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lazyMan = new LazyMan();
            return lazyMan;
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                LazyMan instance = LazyMan.getInstance();
                System.out.println(instance.hashCode());
            }).start();
        }
    }
}

雜湊值是不一樣的,所以懶漢式在併發的情況下是不安全的

加了鎖的懶漢式

public class LazyMan {

    private LazyMan(){}

    public static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(lazyMan == null){
            synchronized(LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan();
                }
            }
            return lazyMan;
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                LazyMan instance = LazyMan.getInstance();
                System.out.println(instance.hashCode());
            }).start();
        }
    }
}

延遲一秒的結果(程式碼中沒有體現)

執行緒A執行到3的位置,執行緒B搶到執行權,停留在1的位置,因為有鎖不能繼續執行,執行緒A建立物件,執行緒B即使能執行到2的位置,但是因為物件不是null,還是不能執行到3的位置,不能建立新的物件。

但是,另一個問題來了
public static LazyMan getInstance(){
    if(lazyMan == null){
        synchronized(LazyMan.class){
            if(lazyMan == null){
                lazyMan = new LazyMan();
                /**
                 * lazyMan = new LazyMan();
                 * 但是這句話並不是一個原子性的,它有三個步驟
                 * 1、開闢一段記憶體地址
                 * 2、執行建構函式,初始化物件
                 * 3、將物件指向這段記憶體地址
                 * 完美的情況下是  1 2 3 ,但是可能 萬一是 1 3 2
                 * 開闢完記憶體地址,指向這段記憶體地址,初始化物件
                 * 此時的初始化物件已經無濟於事,因為已經指向記憶體地址了
                 * 這就導致,不是原子性的問題帶來的影響
                 */
            }
        }
        return lazyMan;
    }
    return lazyMan;
}

所以我們需要加上volatile

但是我們依舊可以通過反射來破壞
package com.xiaofei.single;

import java.lang.reflect.Constructor;
import java.util.concurrent.TimeUnit;

/**
 * @author xiaofei
 * @version 1.0
 * @date 2020/9/12 18:47
 */

public class LazyMan {

    private LazyMan(){}

    public volatile static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(lazyMan == null){
            synchronized(LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan();
                }
            }
            return lazyMan;
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        LazyMan lazyMan1 = LazyMan.getInstance();
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan lazyMan2 = constructor.newInstance();
        System.out.println(lazyMan1.hashCode());
        System.out.println(lazyMan2.hashCode());
    }
}

可以發現,反射依舊可以破壞

下面我們用到終極辦法:列舉

public enum  SingleEnum {

    SINGLENUM;
}
class Test{
    public static void main(String[] args) {
        SingleEnum singlenum1 = SingleEnum.SINGLENUM;
        SingleEnum singlenum2 = SingleEnum.SINGLENUM;
        System.out.println(singlenum1.hashCode());
        System.out.println(singlenum2.hashCode());
    }
}

列舉寫法簡單,而且不會被反射破壞