1. 程式人生 > >單例模式與多執行緒

單例模式與多執行緒

如果一個類是單例類,那麼這個類只能例項化一個例項,並且單例類必須能夠自己建立自己的唯一例項(所以單例類的構造器必須是私有的);

使用背景:

一個全域性使用的類頻繁地建立與銷燬,比如之前我曾負責人員電子檔案開發,這裡涉及到一個紅名單的概念,比如我們要檢視某個人的檔案,但是每次檢視檔案前都需要判斷這個人是否為紅名單中的人物,如果是的話那麼就無許可權檢視該人員的檔案。這裡的紅名單其實就一系列儲存在資料庫中的身份證,而我們每次檢視某個人的檔案時都需要進行一次資料庫互動,判斷是否為紅名單中的人物,我們知道頻繁與資料庫互動並不是什麼好事,而單例模式在這裡可以很好的解決這個問題,我們再系統啟動的時候可以將紅名單中的資料載入到快取中,之後每次都來快取取這個紅名單並判斷當前被查人員是否為紅名單人員即可。
單例模式分為立即載入(餓漢模式)和延遲載入(懶漢模式)

立即載入

立即載入是指在呼叫方法呼叫前,例項就已經被建立快取在記憶體中,且看如下程式碼:

public class MyObject {
    private static MyObject myObject = new MyObject();
    private MyObject() { 
    }
    public static MyObject getInstance() {
            return myObject;
    }
}

這種建立方式存在一種缺陷是該myObject單例中不能存放其他的變數,如果有MyObject中存在其他變數,則需要在getInstance中進行初始化操作,但是因為getInstance()方法沒有同步,所以有可能出現非執行緒安全的問題。

延遲載入

延遲載入是指在呼叫get()方法時例項才被建立,常見的實現辦法就是在get()方法中進行new例項化,如:

public class MyObject {
    private static MyObject myObject = new MyObject();
    private MyObject() { 
    }
    public static MyObject getInstance() {
        //在多執行緒環境中,該程式碼無法實現保持單例
         if (myObject == null) {
            myObject = new
MyObject(); } return myObject; } }

使用延遲載入MyObject類在單執行緒情況確實實現了單例模式,但是在多執行緒併發下就有可能出現多個MyObject的例項,因為getInstance()不是同步方法,我們可以在getInstance()方法上加入同步synchronized關鍵字來解決多執行緒下單例問題,但是這種方法有時候效率會非常低下,因為是同步執行的,下一個執行緒想要取得物件,則必須等上一個執行緒釋放鎖之後才可以繼續執行。我們再來看看下面這段程式碼:

public class MyObject {
    private volatile static MyObject myObject;
    private MyObject() { 
    }
    public static MyObject getInstance() {
            if (myObject == null) {
                synchronized (MyObject.class) {
                    myObject = new MyObject();
                }
            }

        return myObject;
    }
}

如上程式碼,我們使用同步程式碼塊或使用lock來保證關鍵的程式碼部分同步,而其他程式碼不需要同步,但是這樣還是會存在非執行緒安全,因為有可能會存在多個執行緒在lock(obj)這個地方進行排隊例項化,這樣依然會例項化出多個不同的例項物件。

解決多執行緒單例的方法:

雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)

public class MyObject {
    private volatile static MyObject myObject;
    private MyObject() {
    }

    // 使用雙檢測機制來解決問題,即保證了不需要同步程式碼的非同步,又保證了單例的效果
    public static MyObject getInstance() {
            if (myObject == null) {
                synchronized (MyObject.class) {
                    if (myObject == null) {
                        myObject = new MyObject();
                    }
                }
            }
        return myObject;
    }
}

使用內建靜態類實現

public class MyObject implements Serializable {
    private static final long serialVersionUID = 888L;
    private MyObject() { }

    public static MyObject getInstance() {
        return MyObjectHandler.myObject;
    }

    //如果註釋掉該介面方法,則無法實現序列化和反序列化在單例模式中的實現
    protected Object readResolve() throws ObjectStreamException {
        System.out.println("呼叫了readResolve方法!");
        return MyObjectHandler.myObject;
    }

    // 內部類
    private static class MyObjectHandler {
        private static final MyObject myObject = new MyObject();
    }
}
public class SaveAndRead {

    public static void main(String[] args) {
        try {
            MyObject myObject = MyObject.getInstance();
            FileOutputStream fosRef = new FileOutputStream(new File("myObjectFile.txt"));
            ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
            oosRef.writeObject(myObject);
            oosRef.close();
            fosRef.close();
            System.out.println(myObject.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            FileInputStream fisRef = new FileInputStream(new File("myObjectFile.txt"));
            ObjectInputStream iosRef = new ObjectInputStream(fisRef);
            MyObject myObject = (MyObject) iosRef.readObject();
            iosRef.close();
            fisRef.close();
            System.out.println(myObject.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

執行結果:
29310343
呼叫了readResolve方法!
29310343

使用static程式碼塊實現

靜態程式碼塊中的程式碼在使用的時候寄已經執行了,所以可以該特性來實現單例設計模式。

public class MyObject {
    private static MyObject instance = null;
    private MyObject() { }

    static {
        instance = new MyObject();
    }

    public static MyObject getInstance() {
        return instance;
    }
}
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(MyObject.getInstance().hashCode());
        }
    }
}
public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}
執行結果:
17388941
17388941
17388941
17388941
17388941
17388941
17388941
17388941
17388941

使用enum列舉資料型別實現

列舉enum和靜態程式碼塊的特性相似,在使用列舉類時,構造方法會被自動呼叫,也可以使用該特性實現單例設計單例模式。

public class MyObject {
    public enum MyEnumSingleton {
        connectionFactory;
        private Connection connection;

        private MyEnumSingleton() {
            try {
                System.out.println("建立MyObject物件");
                String url = "jdbc:sqlserver://localhost:1079;databaseName=y2";
                String username = "sa";
                String password = "";
                String driverName = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
                Class.forName(driverName);
                connection = DriverManager.getConnection(url, username, password);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        public Connection getConnection() {
            return connection;
        }
    }

    public static Connection getConnection() {
        return MyEnumSingleton.connectionFactory.getConnection();
    }
}
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(MyObject.getConnection().hashCode());
        }
    }
}
public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}
執行結果:
建立MyObject物件
10440721
10440721
10440721
10440721
10440721
10440721
10440721
10440721
10440721