单例模式的实现方式

设计模式

单例模式

单例模式,顾名思义就是一个类只能有一个实例。在系统应用中一个类只有一个对象实例。
单例模式分为五种

1.饿汉模式

在类加载时就为类创建一个对象实例。
实现方式有两种

1.1. 直接为对象创建实例

public class HungrySingleton {
    private static final HungrySingleton hungrySingleton = new HungrySingleton();
    //构造方法需要变为私有,提供getInstance获取对象实例
    private HungrySingleton(){}
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

1.2. 在static静态代码块中创建实例

public class HungrySingleton {
    private static final HungrySingleton hungrySingleton;
    static {
        hungrySingleton = new HungrySingleton();
    }
    //构造方法需要变为私有,提供getInstance获取对象实例
    private HungrySingleton(){}
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

2.懒汉模式

在需要的时候对类进行实例化,创建对象实例。

2.1. 懒汉简单模式

public class LazySimpleSingleton {
    private static LazySimpleSingleton lazySimpleSingleton = null;
    private LazySimpleSingleton(){}
    public static LazySimpleSingleton getInstance(){
        if (lazySimpleSingleton == null){
            lazySimpleSingleton = new LazySimpleSingleton();
        }
        return lazySimpleSingleton;
    }
}

缺点: 易产生线程不安全。

2.2. 懒汉线程安全模式

使用synchronized保证线程安全

public class LazySimpleSingleton {
    private static LazySimpleSingleton lazySimpleSingleton = null;
    private LazySimpleSingleton(){}
    public static synchronized LazySimpleSingleton getInstance(){
        if (lazySimpleSingleton == null){
            lazySimpleSingleton = new LazySimpleSingleton();
        }
        return lazySimpleSingleton;
    }
}

缺点: 虽说jdk1.6之后对synchronized做了性能优化,但是还是会有一些性能影响,容易造成这个类都被锁住,所以使用如下2.2.3.更优化的一种方式实现。

2.2.3. 双重check

说明:
CPU执行时会转换成JVM指令执行
会出现2./3.指令重排序的问题,使用volatile解决(对所有线程可见)
1. 分配内存给这个对象
2. 初始化这个对象
3. 将初始化好的对象和内存地址建立关联,赋值。
4. 用户初次访问

public class LazyDoubleSingleton {
    private static volatile LazyDoubleSingleton lazyDoubleSingleton = null;
    private LazyDoubleSingleton(){}
    public static LazyDoubleSingleton getInstance(){
        if (lazyDoubleSingleton == null){
            synchronized(LazyDoubleSingleton.class){
                if(lazyDoubleSingleton == null)
                    lazyDoubleSingleton = new LazyDoubleSingleton();
            }
        }
        return lazyDoubleSingleton;
    }
}

2.2.4 单例模式中最优方式— 内部类方式

/**
 * 此种方式最优,内部类变量只有在外部方法调用的时候才会被实例化
 * 类加载顺序 :按照静态代码的顺序加载,构造函数最后,静态内部类不调用不会被初始化。
 */
public class LazyInnerClassSingleton {

    /**
     * 使用此种判断可以防止被反射创建实例
     * RuntimeException属于比较严重的错误,系统问题或程序员写的代码有误,JVM必须停止运行改正错误,
     * 所以系统不会继续运行,但Exception会继续运行。
     */
    private LazyInnerClassSingleton(){
        if (LazyHolder.LAZY_SINGLETON != null ){
            throw new RuntimeException("对象实例已存在。。。");
        }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return LazyHolder.LAZY_SINGLETON;
    }

    private static class LazyHolder{
        public static final LazyInnerClassSingleton LAZY_SINGLETON = new LazyInnerClassSingleton();
    }

    public static void main(String[] args) {
        try{
            Constructor constructor = LazyInnerClassSingleton.class.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            LazyInnerClassSingleton l = (LazyInnerClassSingleton)constructor.newInstance(null);
            LazyInnerClassSingleton l2 = LazyHolder.LAZY_SINGLETON;
            System.out.println(l == l2);
            // 运行结果:抛出异常
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

说明:因为构造方法中加了为空抛异常的判断,所以在使用反射创建实例的时候会抛出异常。

3. 序列化单例

序列化单例即用实现序列化接口创建单例。


import java.io.Serializable;

/**
 * 实现序列化
 */
public class SerializableSingleton implements Serializable {
    private static final SerializableSingleton SINGLEION = new SerializableSingleton();
    //构造方法需要变为私有,提供getInstance获取对象实例
    private SerializableSingleton(){}
    public static SerializableSingleton getInstance(){
        return SINGLEION;
    }

    /**
     * 在ObjectInputSteam中有下面一段代碼確定是否需要新建實例
     * if (obj != null &&
     *             handles.lookupException(passHandle) == null &&
     *             desc.hasReadResolveMethod())
     * 實際上是創建了兩個實例,只不過由readResolve判斷是否已經有實例,有則覆盖反序列化
     * 生成的對象實例  。
     * @return
     */
    private Object readResolve(){
        return SINGLEION;
    }
}

说明: 在没有readResolve方法可以通过反序列化的方式创建新的对象实例,如以下代码所示,加上就可以阻止被反序列化,原因看代码中的注解。

import com.zm.singleton.serializable.SerializableSingleton;

import java.io.*;

public class SerializableSingletonTest {
    public static void main(String[] args) {
        SerializableSingleton singleton1 = null;
        SerializableSingleton singleton2 = SerializableSingleton.getInstance();

        try{
            FileOutputStream fos = new FileOutputStream("SerializableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(singleton2);
            oos.flush();
            oos.close();
            fos.close();

            FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            singleton1 = (SerializableSingleton) ois.readObject();
            ois.close();
            fis.close();

            System.out.println(singleton1 == singleton2);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

4.注册式单例

4.1 容器式单例

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {
    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
    private ContainerSingleton(){}

    /**
     * 不加synchronized會出現線程不安全問題,因為ConcurrentHashMap只控制自己內部的方法
     * 而當前方法並不受控制。
     * @param className
     * @return
     */
    public static Object getInstance(String className){
        try {
            synchronized (ioc) {
                if (!ioc.containsKey(className)) {
                    Object instance = Class.forName(className).newInstance();
                    ioc.put(className, instance);
                    return instance;
                } else {
                    return ioc.get(className);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

注意: 容器式单例的创建方法中需要加上synchronized,原因见注释。

4.2 枚举式单例

使用枚举的方式创建单例,
优点: 简单、方便,不需要new对象。


/**
 * 枚举式單例,即註冊式單例。
 * 在jdk层面就为枚举类单例不被反序列化和反射保驾护航
 * 在枚举的class文件中可以看到INSTANCE初始化代碼是放在static代码块中,以饿汉式的方式创建实例。
 * 枚举式的class文件中构造函数带有参数
 * 只會被初始化一次
 */
public enum EnumSingleton {
    INSTANCE;
   // 用于测试两个对象是否相同
    private Object data;

    public static EnumSingleton getInstance(){return INSTANCE;}

    public Object getData() {
        return data;
    }
}

5. ThreadLocal方式实现单例

public class ThreadLocalSingleton {
    private ThreadLocalSingleton(){}

    private static final ThreadLocal<ThreadLocalSingleton> threadLocalSingleton = new ThreadLocal<ThreadLocalSingleton>(){
        @Override
        protected ThreadLocalSingleton initialValue(){
            return new ThreadLocalSingleton();
        }
    };

    public static ThreadLocalSingleton getInstance(){
        return threadLocalSingleton.get();
    }
}

缺点: 存在线程不安全问题,在ThreadLocal本线程内安全,但在其他线程中就容易出现线程不安全。

猜你喜欢

转载自blog.csdn.net/weixin_41224131/article/details/88563803