1. 程式人生 > >深入理解java多執行緒(六)

深入理解java多執行緒(六)

關於java多執行緒的概念以及基本用法:java多執行緒基礎

6,單例模式與多執行緒

如何使單例模式遇到多執行緒是安全的這是下面要討論的內容

6.1,立即載入

立即載入就是在使用類的時候已經將物件建立完畢,例如String s = new String(”hello world!”);這裡就是直接將物件例項化了
MyObject類:

public class MyObject {

    private static MyObject myObject = new MyObject();

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

測試類:

public class Run extends Thread{

    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }

    public static void main(String[] args) {
        Run run1 = new
Run(); Run run2 = new Run(); Run run3 = new Run(); run1.start(); run2.start(); run3.start(); } }

結果:

1141021789
1141021789
1141021789

列印的hashCode是同一個值,說明物件時同一個

6.2,延遲載入

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

MyObject類:

public class
MyObject{ public static MyObject myObject; private MyObject() { } public static MyObject getInstance() { if(myObject != null) { }else { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } myObject = new MyObject(); } return myObject; } }

測試類:

public class Run extends Thread{

    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }

    public static void main(String[] args) {
        Run run1 = new Run();
        Run run2 = new Run();
        Run run3 = new Run();
        run1.start();
        run2.start();
        run3.start();
    }

}

結果:

1796851831
141757079
1721087309

延遲載入顯然不是單例,那麼如何讓延遲載入變成單例呢,可以用synchronized關鍵字和同步程式碼塊

1,延遲載入同步方案–synchronized關鍵字
上面的程式碼不變,
public static MyObject getInstance()->synchronized public static MyObject getInstance()
執行結果顯示hashcode值都是一樣的,所以這種方法是可行的

2,延遲載入同步方案–同步程式碼塊
public static MyObject getInstance()改為:

    public static MyObject getInstance()  {

        synchronized(MyObject.class) {
            if(myObject != null) {
            }else {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                myObject = new MyObject();
            }

        }
        return myObject;
    }

這樣做也可以實現單例,但是這兩種方法都有一個問題就是,效率比較低,都把整個方法給鎖住了,下面嘗試對部分程式碼同步

3,延遲載入同步方案–針對重要程式碼單獨同步
public static MyObject getInstance()改為:

    public static MyObject getInstance()  {

            if(myObject != null) {
            }else {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //部分程式碼被上鎖,但存在非執行緒安全問題
                synchronized (MyObject.class) {
                    myObject = new MyObject();
                }

            }
        return myObject;
    }

結果:

1721087309
141757079
1796851831

對部分程式碼實現同步,執行效率得到了提高,但是單例卻實現不了了

4,延遲載入同步方案–使用DCL雙檢查鎖機制
DCL雙檢查鎖機制是實現多執行緒環境中延遲載入單例設計模式

public class MyObject{

    private volatile static MyObject myObject;
    private MyObject() {

    }
    public static MyObject getInstance()  {

            if(myObject != null) {
            }else { 
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        // TODO 自動生成的 catch 塊
                        e.printStackTrace();
                    }

                //部分程式碼被上鎖,但存在非執行緒安全問題
                synchronized (MyObject.class) {
                    if(myObject ==null) {
                        myObject = new MyObject();
                    }

                }

            }

        return myObject;
    }
}

結果:

141757079
141757079
141757079

實現了單例

4,延遲載入同步方案–靜態內部類

之前做一個安卓-RFID讀卡器實現打卡的App,當時做到資料獲取時,老是拿不到資料,因為當時的程式並不是單例,而用靜態內部類可以很好的實現,單例模式

public class Myobject{
    private static class MyObjectHandler{
        private static MyObject myObject = new MyObject();
    }

    private MyObject() {

    }

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

結果當然是可以實現單例的

5,序列化與反序列化的單例模式實現

靜態內建類可以實現執行緒安全,但是如果遇到了序列化物件,使用預設的方式執行的結果則會是多例
新建MyObject類:

public class MyObject implements Serializable{

    private static final long serialVersionUID = 888L;

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

    private MyObject() {}

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

    /*protected Object readResolve() throws ObjectStreamException{
        System.out.println("111");
        return MyObjectHandler.myObject;
    }*/

}

SaveAndRead類:

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.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 e1) {
            e1.printStackTrace();
        }catch(ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

這樣執行得到的結果不是同一個物件,解決辦法就是去掉註釋使用readResolve()方法

6,使用static程式碼塊實現單例模式
由於靜態程式碼塊是在使用類時就執行了的,所以藉助這一特性,可以實現單例

新建MyObject類:

public class MyObject {

    private static MyObject instance = null;
    private MyObject() {}
    static {
        instance = new MyObject();
    }
    public static MyObject getInstance() {
        return instance;
    }
}

測試類:

public class Run extends Thread{

    @Override
    public void run() {
        for(int i=0;i<5;i++) {
            System.out.println(MyObject.getInstance().hashCode());
        }
    }

    public static void main(String[] args) {
        Run run1 = new Run();
        Run run2 = new Run();
        Run run3 = new Run();
        run1.start();
        run2.start();
        run3.start();
    }
}

結果:

1721087309
1721087309
1721087309
。。。。
。。。。
。。。。

7,使用enum列舉資料型別實現單例模式
新建MyObject類:


public class MyObject {

    private enum MyEnumSingleton{
        INSTANCE;
        private Resource resource;

        private MyEnumSingleton(){
            resource = new Resource();
        }

        public Resource getResource(){
            return resource;
        }
    }

    public static Resource getResource(){
        return MyEnumSingleton.INSTANCE.getResource();
    }
}

測試類:


public class Run {  
    class MyThread extends Thread {  

        @Override  
        public void run() {  
            for (int i = 0; i < 5; i++) {  
                System.out.println(MyObject.getResource().hashCode());  
            }  
        }  
    }  
    public static void main(String[] args) {  
        Run.MyThread t1 = new Run().new MyThread();  
        Run.MyThread t2 = new Run().new MyThread();  
        Run.MyThread t3 = new Run().new MyThread();  

        t1.start();  
        t2.start();  
        t3.start();  

    }  

}

結果當然是單例啦