单例模式遇上多线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq1169091731/article/details/83067082

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

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

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;
    }
  1. 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;
    }

结论:懒汉模式不能直接用于多线程中,会导致创建多个实例,用双重检查锁机制修正,保证在多线程环境下只会创建单例对象

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 测试,适用于多线程环境下

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

}

猜你喜欢

转载自blog.csdn.net/qq1169091731/article/details/83067082
今日推荐