美文网首页
单例模式与多线程

单例模式与多线程

作者: onlyHalfSoul | 来源:发表于2018-05-25 15:07 被阅读17次

关键

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

立即加载/"饿汉模式"

立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接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多线程--并行模式与算法

    Java多线程--并行模式与算法 单例模式 虽然单例模式和并行没有直接关系,但是我们经常会在多线程中使用到单例。单...

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • 单例模式

    单例模式介绍 把单例模式放到多线程基础这块,是因为单例和多线程有一点的关系。何为单例模式? 在它的核心结构中只包含...

  • Unity3d游戏开发之-单例设计模式-多线程一

    单例模式3:多线程一

  • 单例模式

    单例模式 单例模式:用来保证一个对象只能被创建一次。 普通版 代码实现如下 同步锁单例 单例模式如果再多线程中使用...

  • 多线程(下)&GUI

    day25(多线程(下)&GUI) 1_多线程(单例设计模式)(掌握) 单例设计模式:保证类在内存中只有一个对象。...

  • 设计模式——单例模式的破坏

    概述: 之前学习了单例模式的几种实现,解决了多线程情况下,单例的线程安全问题,保证了单例的实现。但是单例模式在下面...

  • iOS 多线程NSThread,GCD,NSOperation

    单例模式例子: https://github.com/XiaoRuiZuo/Singleton 多线程:多线程是为...

  • day25-多线程/Timer/单例模式/工厂模式/GUI

    25.01_单例设计模式(掌握) 单例设计模式:保证类在内存中只有一个对象。 25.02_多线程(Runtime类...

  • 25.01_多线程(单例设计模式)

    ###25.01_多线程(单例设计模式)(掌握) * 单例设计模式:保证类在内存中只有一个对象。 * 如何保证类在...

网友评论

      本文标题:单例模式与多线程

      本文链接:https://www.haomeiwen.com/subject/wnfujftx.html