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

单例模式遇上多线程

作者: Ethan_Walker | 来源:发表于2018-05-12 09:57 被阅读5次

1. 饿汉模式——立即加载

public class MySingleObj {
    private static MySingleObj mySingleObj = new MySingleObj();

    public static MySingleObj getInstance() {
        return mySingleObj;
    }
    // 将构造函数私有化,不让外界直接创建对象
    private MySingleObj(){}
}

测试类: TestSingle.java

public class TestSingle {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(MySingleObj.getInstance());
            }
        };
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(runnable);
        }
        for (int i = 0; i < 100; i++) {
            threads[i].start();
        }
    }
}

结论: 得到的始终是单例对象,饿汉模式可以直接用于多线程中

为什么饿汉模式多线程安全呢?

  • 单例对象是类变量,随着类的加载而初始化,一次运行类只加载一次,类变量只会初始化一次,而且类加载前,先要获得Class对象锁,加载类和初始化类变量的过程中只允许一个线程进入过,故线程安全。

2. 懒汉模式——延迟加载

public class MySingleObj2 {
    private static MySingleObj2 mySingleObj2;
    private MySingleObj2(){}

    public static MySingleObj2 getInstance() {
        try {
            if (mySingleObj2 == null) {
                Thread.sleep(100); // 理解为创建对象前的初始化操作
                mySingleObj2 = new MySingleObj2();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return mySingleObj2;
    }

}

TestSingle.java进行测试,发现得到的不是单例对象,不多线程环境下不能保持单例状态,如何修正呢?
分析错误原因:
有多个线程同时进入到 判断语句 if (mySingleObj2 == null),故每个线程都会各自创建对象。

修正:

  1. 同步方法: 将 getInstance()方法前加上 synchronized关键字,保证该方法同步,每一次只能由一个线程访问。优点:简单。缺点:效率太低
    多个线程并发,这样的话线程只能顺序执行

  2. synchronized 同步代码块

  • synchronizedif (mySingleObj2 == null) {前,即包含整个代码,这样做虽然能够保证只创建一个单例,但是效率和 同步方法一样差

  • synchronized 只包含创建对象的代码块, 缺点:仍然会导致创建多个对象

 public static MySingleObj2 getInstance2() {
        try {
            if (mySingleObj2 == null) {
                Thread.sleep(100);
                // 仍然会出现线程不安全的问题
                synchronized (MySingleObj2.class) {
                    mySingleObj2 = new MySingleObj2();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return mySingleObj2;
    }

3. DCL 双重检查锁机制

public static MySingleObj2 getInstance3() {
        try {
            if (mySingleObj2 == null) {
                Thread.sleep(1000);
                synchronized (MySingleObj2.class) {
                    // 检查两次,获取最新值,防止之前锁中的线程已经对 obj 做出了改变
                    if (mySingleObj2 == null) {
                        mySingleObj2 = new MySingleObj2();
                    }
                }
            }
        } catch (InterruptedException e) {

        }
        return mySingleObj2;
    }

双重检查锁机制貌似解决了延迟初始化情况下多线程不安全的情况,但是其实并没有解决。
根源:instance = new Object();创建对象可以理解为分解为下面三步

  1. 分配对象所需的内存空间
  2. 初始化对象
  3. 将引用指向对象分配的内存地址

如果程序底层的确按照这三步进行,就没问题。但是,JMM可能会将 2、3 重排序,即对象还未初始化,就已经将引用指向分配的内存地址了。这时线程判断instance 引用不为 null,如果这时候访问 instance 引用的对象(此时对象还未初始化),就等于访问一个未初始化的对象,导致错误。
解决: 禁止2/3 之间的重排序,如何禁止呢?声明 instance 为 volatile 变量即可

3. 静态内置类创建单例模式


public class MySingleObj {
    private static class MySingletonHandler{
        private static MySingleObj mySingleObj = new MySingleObj();
    }
    public static MySingleObj getInstance(){
        return MySingletonHandler.mySingleObj;
    }
    
    private MySingleObj(){}
}

用 TestSingle.java 测试,适用于多线程环境下
原理:在访问内置类Class对象时,会获取一个锁,保证多线程环境下只允许一个线程能获取该锁,从而保证该类只被加载、初始化一次,其他线程等待锁释放后获得锁,就不必再加载类、初始化类变量了。从而保证内部类中的类变量始终为单例对象。

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

静态内置类可以达到线程安全、创建单例的目的,但是当序列化对象、反序列化时,得到的对象是多例的。

public class MySingleObj implements Serializable{

    private static final long serialVersionUID = 7130249135107591053L;

    private MySingleObj(){}
    private static class MySingleObjHandler{
        private static MySingleObj mySingleObj = new MySingleObj();
    }

    public static MySingleObj getInstance() {
        return MySingleObjHandler.mySingleObj;
    }
    /*
    protected Object readResolve(){
        System.out.println("调用了readResolve()方法");
        return MySingleObjHandler.mySingleObj;
    }
    */
}

序列化和反序列化测试类

public class SerializableTest {
    public static void main(String[] args) {
        serializable();
        deserializable();
    }

    public static void serializable() {
        MySingleObj mySingleObj = MySingleObj.getInstance();
        ObjectOutputStream outputStream = null;
        try {
            outputStream = new ObjectOutputStream(new FileOutputStream(new File("a.txt")));
            outputStream.writeObject(mySingleObj);
            System.out.println(mySingleObj);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void deserializable(){
        ObjectInputStream inputStream = null;

        try {
            inputStream = new ObjectInputStream(new FileInputStream(new File("a.txt")));
            MySingleObj mySingleObj = (MySingleObj) inputStream.readObject();
            System.out.println(mySingleObj);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(inputStream!=null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

输出结果:

pattern4.MySingleObj@6d6f6e28
pattern4.MySingleObj@7cca494b

即序列化的对象,进行反序列化后得到的不是同一个实例

如何解决:
MySingleObj 中注释的readResolve()方法放开即可,反序列化时会调用 readResolve()方法

再次运行

pattern4.MySingleObj@6d6f6e28
调用了readResolve()方法
pattern4.MySingleObj@6d6f6e28

结论: 静态内置类创建单例的方式不能直接用于序列化和反序列化,要加上readResolve() 方法供反序列化时调用,保证反序列化后创建的对象和序列化创建的对象为同一个

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

public class MySingleObj{
    private static MySingleObj obj =null;
    static{
        obj = new MySingleObj();
    }
    private MySingleObj(){}

    public static MySingleObj getInstance() {
        return obj;
    }
}

结论:类似于饿汉模式,适用于多线程环境

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

枚举enum 和静态代码块类似,使用枚举类时,其构造方法会自动调用,且只调用一次,利用该特性实现单例模式

public enum MySingleObj {
    MY_SINGLE_OBJ;

    private Object object;
    private MySingleObj(){
        object = new Object();
    }

    public  Object getInstance() {
        return object;
    }

}

TestSingle.java

public class TestSingle {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(MySingleObj.MY_SINGLE_OBJ.getInstance());
            }
        };
        Thread[] threads = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threads[i] = new Thread(runnable);
        }
        for (int i = 0; i < 100; i++) {
            threads[i].start();
        }
    }
}

结果:正确,只创建了单例

完善 enum 类创建单例模式

上面的方式,将枚举类暴露,违反‘’职责单一原则’,修改MySingleObj.java

public class MySingleObj {
    public enum MyEnumSingleton {
        MY_SINGLE_OBJ;
        private Object object;
        private MyEnumSingleton() {
            object = new Object();
        }
        public Object getInstance() {
            return object;
        }
    }
    public static Object getInstance() {
        return MyEnumSingleton.MY_SINGLE_OBJ.getInstance();
    }

}

相关文章

  • 单例模式Java篇

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

  • Java多线程--并行模式与算法

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

  • 单例模式

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

  • 单例模式遇上多线程

    1. 饿汉模式——立即加载 测试类: TestSingle.java 结论: 得到的始终是单例对象,饿汉模式可以直...

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

    单例模式3:多线程一

  • 单例模式

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

  • 多线程(下)&GUI

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

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

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

  • iOS 多线程NSThread,GCD,NSOperation

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

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

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

网友评论

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

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