设计模式之单例模式

单例模式

1.懒汉模式:延迟加载,只有在真正使用的时候,才开始初始化。

1)线程安全问题

2)double check 加锁优化

3)编译器(JIT)、CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰

对于volatile修饰的字段,可以防止指令重排。

public class LazySingletonTest {
    public static void main(String[] args) {
        LazySingleton instance = LazySingleton.getInstance();
        LazySingleton instance1 = LazySingleton.getInstance();
        System.out.println(instance == instance1);
    }
}
class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){}
    public static LazySingleton getInstance() {
        if(instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }
}

在多线程环境下会出现问题:如下面代码:

public class LazySingletonTest {
    public static void main(String[] args) {
       new Thread(()->{
           LazySingleton instance = LazySingleton.getInstance();
           System.out.println(instance);
       }).start();
        new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();
    }
}
class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){}
    public static LazySingleton getInstance() {
        if(instance == null){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingleton();
        }
        return instance;
    }
}

打印结果:

com.kzp.designpattern.lazysingleton.LazySingleton@206683de
com.kzp.designpattern.lazysingleton.LazySingleton@37d3a78

在多线程环境下,打印了两个不同的对象,getInstance方法前加入synchronized关键字可解决该多线程问题。

public class LazySingletonTest {
    public static void main(String[] args) {
       /* LazySingleton instance = LazySingleton.getInstance();
        LazySingleton instance1 = LazySingleton.getInstance();
        System.out.println(instance == instance1);*/
       new Thread(()->{
           LazySingleton instance = LazySingleton.getInstance();
           System.out.println(instance);
       }).start();
        new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();
    }
}
class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){}
    public synchronized static LazySingleton getInstance() {
        if(instance == null){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingleton();
        }
        return instance;
    }
}

但加入synchronized会有性能问题,下面是用双重检查机制来创建单例对象。

public class LazySingletonTest {
    public static void main(String[] args) {
       /* LazySingleton instance = LazySingleton.getInstance();
        LazySingleton instance1 = LazySingleton.getInstance();
        System.out.println(instance == instance1);*/
       new Thread(()->{
           LazySingleton instance = LazySingleton.getInstance();
           System.out.println(instance);
       }).start();
        new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        }).start();
    }
}
class LazySingleton{
    private volatile static LazySingleton instance;
    private LazySingleton(){}
    public static LazySingleton getInstance() {
        if(instance == null){
            synchronized (LazySingleton.class){
                if(instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

初始化对象的步骤为:

//1.分配空间

//2.初始化

//3.引用赋值

加入volatile可以防止指令的重排序

 

2.饿汉模式:

类加载的初始化阶段就完成了实例的初始化,本质上就是借助于JVM类加载机制,保证实例的唯一性。

类加载过程:

1.加载二进制数据到内存中,生成对应的Class数据结构。

2.连接:a:验证 b:准备(给类的静态成员变量赋默认值)c:解析

3.初始化:给类的静态变量赋初值。

只有在真正使用对应的类时,才会触发初始化 如(当前类是启动类即main函数所在类,直接进行new 操作,访问静态属性,访问静态方法,

用反射访问类,初始化一个类的子类等)

 

public class HungrySingletonTest {
    public static void main(String[] args) {
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton instance1 = HungrySingleton.getInstance();
        System.out.println(instance == instance1);
    }
}
class HungrySingleton{
    private static HungrySingleton instance = new HungrySingleton();
    private HungrySingleton(){
    }
    public static HungrySingleton getInstance() {
        return instance;
    }
}

 

3.静态内部类

1)本质上是利用类的加载机制来保证线程安全。

2)只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。

public class InnerClassSingletonTest {
    public static void main(String[] args) {
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
        System.out.println(instance == instance1);
    }
}
class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){

    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

 

多线程下代码:

public class InnerClassSingletonTest {
    public static void main(String[] args) {
        new Thread(()->{
            InnerClassSingleton instance = InnerClassSingleton.getInstance();
            System.out.println(instance);
        }).start();
        new Thread(()->{
            InnerClassSingleton instance = InnerClassSingleton.getInstance();
            System.out.println(instance);
        }).start();
    }
}
class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){

    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

 

打印结果为

com.kzp.designpattern.innerclasssingleton.InnerClassSingleton@206683de
com.kzp.designpattern.innerclasssingleton.InnerClassSingleton@206683de

 

因此这种方式是线程安全的。

但是,可以通过发射来创建多实例,违反单例模式:

public class InnerClassSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        System.out.println(innerClassSingleton == instance);
    }
}
class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){

    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

 

但静态内部类和饿汉模式可以防止创建多实例,懒汉模式则不可以

通过在私有构造函数中判断对象是否存在,存在则抛出异常来避免反射创建多实例。

public class InnerClassSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        System.out.println(innerClassSingleton == instance);
    }
}
class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if(InnerClassHolder.instance != null){
            throw  new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

 

4.枚举单实例方式

public enum  EnumSingleton {
    INSTANCE;
    public void print(){
        System.out.println(this.hashCode());
    }
}
class EnumTest{
    public static void main(String[] args) {
        EnumSingleton instance = EnumSingleton.INSTANCE;
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        System.out.println(instance == instance1);
    }
}

 

下面通过反射来创建枚举单实例对象

public enum  EnumSingleton {
    INSTANCE;
    public void print(){
        System.out.println(this.hashCode());
    }
}
class EnumTest{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
       /* EnumSingleton instance = EnumSingleton.INSTANCE;
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        System.out.println(instance == instance1);*/
        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        declaredConstructor.newInstance("INSTANCE",0);
    }
}

 

抛出异常

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects

at java.lang.reflect.Constructor.newInstance(Constructor.java:402)

at com.kzp.designpattern.enumsingleton.EnumTest.main(EnumSingleton.java:19)

序列化与反序列化实例对象后,单例对象遭到了破坏。

public class InnerClassSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
       //测试序列化
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
        oos.writeObject(instance);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
        InnerClassSingleton object = (InnerClassSingleton) ois.readObject();
        System.out.println(instance == object);
    }
}
class InnerClassSingleton implements Serializable {
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if(InnerClassHolder.instance != null){
            throw  new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

 

结果返回false

在Serializable接口中有这样的描述:

Classes that need to designate a replacement when an instance of it
* is read from the stream should implement this special method with the
* exact signature.
*
* <PRE>
* ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
* </PRE><p>

 

自定义实现readResolve可解决这个问题

public class InnerClassSingletonTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
       //测试序列化
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
 oos.writeObject(instance);
 oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
        InnerClassSingleton object = (InnerClassSingleton) ois.readObject();
        System.out.println(instance == object);
    }
}
class InnerClassSingleton implements Serializable {
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if(InnerClassHolder.instance != null){
            throw  new RuntimeException("单例不允许多个实例");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }

    Object readResolve() throws ObjectStreamException{
        return InnerClassHolder.instance;
    };
}

 

但是加入这个之后,会报

Exception in thread "main" java.io.InvalidClassException: com.kzp.designpattern.innerclasssingleton.InnerClassSingleton; local class incompatible: stream classdesc serialVersionUID = 595850868840365671, local class serialVersionUID = 7367063990306020841
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
    at com.kzp.designpattern.innerclasssingleton.InnerClassSingletonTest.main(InnerClassSingletonTest.java:32)

 

这是因为我们没有给需要序列化的类给一个serialVersionUID,导致序列化和反序列化时认为为不同的类。

InnerClassSingleton类中加入serialVersionUID即可解决该问题。

枚举类型的单实例则不存在这样的反序列化问题。

public enum  EnumSingleton {
    INSTANCE;
    public void print(){
        System.out.println(this.hashCode());
    }
}
class EnumTest{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
        EnumSingleton instance = EnumSingleton.INSTANCE;
//        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enumsingleton"));
//        oos.writeObject(instance);
//        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enumsingleton"));
        EnumSingleton object = ((EnumSingleton) ois.readObject());
        System.out.println(instance == object);
    }
}

 

结果为true。

源码中的应用

//Spring & JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry
//Tomcat
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
//反序列化指定数据源
java.util.Currency

 

单例模式在JDK中的应用:

Runtime类

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

 

使用到的是饿汉模式。

Currency类中用到了解决反序列化问题的方法。

public final class Currency implements Serializable 
private final String currencyCode;
private Object readResolve() {
    return getInstance(currencyCode);
}
}

 

Spring中DefaultSingletonBeanRegistry类使用到了单例模式

另外,ReactiveAdapterRegistry(Srping5版本才有的类)使用到了双重检查模式:

public class ReactiveAdapterRegistry {
@Nullable
private static volatile ReactiveAdapterRegistry sharedInstance;
public static ReactiveAdapterRegistry getSharedInstance() {
    ReactiveAdapterRegistry registry = sharedInstance;
    if (registry == null) {
        Class var1 = ReactiveAdapterRegistry.class;
        synchronized(ReactiveAdapterRegistry.class) {
            registry = sharedInstance;
            if (registry == null) {
                registry = new ReactiveAdapterRegistry();
                sharedInstance = registry;
            }
        }
    }

    return registry;
}
}
posted @ 2020-01-05 20:29  快乐每一天kzp  阅读(293)  评论(0编辑  收藏  举报