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

单例模式遇上多线程

作者: 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();
        }
    
    }
    

    相关文章

      网友评论

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

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