单例模式与多线程

关键

如何使单例模式遇到多线程是安全的。

立即加载/"饿汉模式"

立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接new实例化。而立即加载在中文语境看来,有"急"的意思,所以也叫"饿汉模式"。

public class MyObject {

    // 立即加载方式==饿汉模式

    /** Field myObject */
    private static MyObject myObject = new MyObject();

    /**
     * Constructs MyObject
     *
     */
    private MyObject() {}

    /**
     * Method getInstance
     *
     *
     * @return
     *
     * 此代码版本为立即加载,此版本代码的缺点是不能有其他实例变量,
     * 因此getinstance()方法没有同步,所以可能出现非线程安全问题。
     */
    public static MyObject getInstance() {
        return myObject;
    }
}


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


public class Run {

    /**
     * Method main
     *
     *
     * @param args
     */
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

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


/*result:
1108767984
1108767984
1108767984
*/

运行结果显示hashCode为同一个值,说明对象时同一个对象,也就实现了立即加载型单例模式。

延迟加载/懒汉模式

延迟加载就是在调用get()方法是实例才被创建,常见的实现办法就是在get()方法中进行new实例化操作。延迟加载又叫懒汉模式。

饿汉模式时会出现多个实例,在多线程环境下这是与单例模式相背离的。

下面的代码是完全错误的,不能实现保持单例的状态。

public class MyObject {

    /** Field myObject */
    private static MyObject myObject;

    /**
     * Constructs MyObject
     *
     */
    private MyObject() {}

    /**
     * Method getInstance
     *
     *
     * @return
     */
    public static MyObject getInstance() {
        try {
            if (myObject != null) {}
            else {

                // 模仿在创建对象时的一些准备工作
                Thread.sleep(3000);
                myObject = new MyObject();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return myObject;
    }
}


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


public class Run {

    /**
     * Method main
     *
     *
     * @param args
     */
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

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


/*result:
1127673581
1516995504
1905381423
*/

此实验会出现取出多个实例的情况,这就是错误的单例模式。

延迟加载的解决方案。

添加sybchronized关键字 ==pass==

在实例化方法中添加synchronized关键字,即可实现得到同一实例,但此方法运行效率非常低,是同步运行的。

public static synchronized MyObject getInstance() {
        try {
            if (myObject != null) { }
            else {

                // 模仿在创建对象时的一些准备工作
                Thread.sleep(3000);
                myObject = new MyObject();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return myObject;
    }
    
    
/*result:
1127673581
1127673581
1127673581
*/    

使用同步代码块 ==pass==

此方法有所改进,但效率依旧比较低。

public static MyObject getInstance() {
        try {
            synchronized (MyObject.class) {
                if (myObject != null) {}
                else {

                    // 模仿在创建对象时的一些准备工作
                    Thread.sleep(3000);
                    myObject = new MyObject();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return myObject;
    }
    
/*result:
853747565
853747565
853747565
*/

针对某些重点代码单独同步 ==pass==

此方法运行效率较高。但多线程情况下无法实现只取一个实例对象。

public static MyObject getInstance() {
        try {
            if (myObject != null) { }
            else {

                // 模仿在创建对象时的一些准备工作
                Thread.sleep(3000);

                synchronized (MyObject.class) {
                    myObject = new MyObject();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return myObject;
    }
    
/*result:
1127673581
936272565
726367991
*/

DCL双检查锁机制。

最后步骤中使用DCL双检查锁机制来实现多线程环境下延迟加载的单例模式,正确且效率高。

public static MyObject getInstance() {
        try {
            if (myObject != null) { }
            else {

                // 模仿在创建对象时的一些准备工作
                Thread.sleep(3000);
                synchronized (MyObject.class) {
                    if (myObject == null) {
                        myObject = new MyObject();
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return myObject;
    }
    
/*result:
1108767984
1108767984
1108767984
*/
    

使用静态内置类实现单例模式

DCL可以解决多线程单例模式的非线程安全问题,但是也有其他方法也能实现。

public class MyObject {

    /**
     * Constructs MyObject
     *
     */
    private MyObject() {}

    /**
     * Method getInstance
     *
     *
     * @return
     */
    public static MyObject getInstance() {
        return MyObjectHandler.myObject;
    }

    /**
     * Class MyObjectHandler
     *
     *
     * @version        1.0, 18/05/05
     * @author         tz
     */
    private static class MyObjectHandler {

        /** Field myObject */
        private static MyObject myObject = new MyObject();
    }
}


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

public class Run {

    /**
     * Method main
     *
     *
     * @param args
     */
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

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

/*result:
264320363
264320363
264320363
*/

上面代码证明使用内置内部类的方法也可以实现懒汉模式下多线程的单实例模式。

序列化和反序列化的单例模式实现

静态内置类可以达到线程安全的目的,但是如果遇到序列化对象时,使用默认的方法运行得到的结果还是多例的。

public class MyObject implements Serializable {

    /** Field serialVersionUID */
    private static final long serialVersionUID = 888L;

    /**
     * Constructs MyObject
     *
     */
    private MyObject() {}

    /**
     * Method getInstance
     *
     *
     * @return
     */
    public static MyObject getInstance() {
        return MyObjectHandler.myObject;
    }

    /**
     * Class MyObjectHandler
     *
     *
     * @version        Enter version here..., 18/05/05
     * @author         Enter your name here...    
     */
    private static class MyObjectHandler {

        /** Field myObject */
        private static final MyObject myObject = new MyObject();
    }
}


public class SaveAndRead {

    /**
     * Method main
     *
     *
     * @param args
     */
    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 (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 (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}


/*result:
1836019240
2093631819
*/

上述程序反应了序列化对象的情况下,内置内部类不能实现单例模式,解决方法就是使用反序列化。

ublic class MyObject implements Serializable {

    /** Field serialVersionUID */
    private static final long serialVersionUID = 888L;

    /**
     * Constructs MyObject
     *
     */
    private MyObject() {}

    /**
     * Method getInstance
     *
     *
     * @return
     */
    public static MyObject getInstance() {
        return MyObjectHandler.myObject;
    }

    protected  Object readResolve() {
        System.out.println("调用了readResolve方法!");
        return MyObjectHandler.myObject;
    }

    /**
     * Class MyObjectHandler
     *
     *
     * @version        Enter version here..., 18/05/05
     * @author         Enter your name here...
     */
    private static class MyObjectHandler {

        /** Field myObject */
        private static final MyObject myObject = new MyObject();
    }
}


/*result:
1836019240
调用了readResolve方法!
1836019240
*/

反序列化后即实现单例。

使用static代码块实现单例模式

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性来实现单例设计模式

public class MyObject {

    /** Field instance */
    private static MyObject instance = null;

    static {
        instance = new MyObject();
    }

    /**
     * Constructs MyObject
     *
     */
    private MyObject() { }

    /**
     * Method getInstance
     *
     *
     * @return
     */
    public static MyObject getInstance() {
        return instance;
    }
}


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


public class Run {

    /**
     * Method main
     *
     *
     * @param args
     */
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

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


/*result:
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
868591785
*/

使用enum枚举数据类型实现单例模式

枚举enum和静态代码块的特性相似,在使用枚举类时构造方法会自动被调用,也可以应用其这个特性实现单例设计模式。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本文的知识点非常重要,通过单例模式与多线程技术相结合,在这个过程中能发现很多以前未考虑过的情况,一些不良的程序设计...
    Java_Explorer阅读 3,880评论 1 2
  • 单例模式简介 想要唯一的创建一个对象,我们不通过约定,而是通过制定约束的方式去限制。虽然我们可以建立一个全局变量。...
    wa1terwen阅读 4,108评论 2 5
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 9,852评论 4 34
  • 1.立即加载/饿汉模式 package singleton;public class MyThread exten...
    有奶喝先森阅读 1,943评论 0 1
  • 向前走吧,做下一个自己。 人生向前走,暂时要做加法,慢慢的做减法。 否定我的人通常没好下场 你愿意帮助那些富有远见...
    导演张升志阅读 1,436评论 0 0