一天一种JAVA设计模式之三:单例模式

263 阅读6分钟

写在前面的话

复习、总结23种设计模式

获取详细源码请点击我

上一篇

# 一天一种JAVA设计模式之二:工厂模式

单例模式

定义

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局的访问点。

单例模式的适用场景

确保任何情况下都绝对只有一个实例。

比如:ServletContext、ServletConfig、ApplicationContext、DBPool等

1.饿汉式单例

定义:在单例类被首次加载时,就创建实例

优点:执行效率高,性能高,没有任何的锁

缺点:某些情况下,可能会造成内存浪费


// 饿汉式:典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,  
// 先创建出来,然后每次调用的时候,就不需要判断了,节省了运行时间
public class HungrySingleton {  
  
    private static final HungrySingleton hungrysingleton = new HungrySingleton();  

    private HungrySingleton() {  

    }  

    public static HungrySingleton getInstance() {  
        return hungrysingleton;  
    }  
}
package com.design.pattern.test01;  
  
/*  
static类加载的时候就创建,final:不可被覆盖。  
内存浪费,占着茅坑不拉屎  
  
*/  
public class Test01饿汉式单例 {  
  
    public static void main(String[] args) {  
        HungrySingleton instance1 = HungrySingleton.getInstance();  
        HungrySingleton instance2 = HungrySingleton.getInstance();  
        System.out.println(instance1 == instance2);  // true
    }  
  
}

2.懒汉式单例

定义:被外部类调用时才创建实例

优点:执行效率高,性能高,没有任何的锁;节约内存

缺点:线程不安全,多线程情况下,就有可能创建多个实例,不能保证一个实例

package com.design.pattern.test02;  
  
public class LazySingleton {  

    //2.本类内部创建对象实例  
    private static LazySingleton singleton;  
    /**  
    * 1.构造方法私有化,外部不能new  
    */  
    private LazySingleton() {  

    }  
    //3.提供一个公有的静态方法,返回实例对象  
    public static LazySingleton getInstance() {  
        if (singleton == null) {  
            singleton = new LazySingleton();  
        }  
        return singleton;  
    }  
  
}
package com.design.pattern.test02;  
  
/*  
线程不安全  
多线程情况下,就有可能创建多个实例,不能保证一个实例  
*/  
public class Test02懒汉式单例 {  
  
    public static void main(String[] args) {  
        LazySingleton instance1 = LazySingleton.getInstance();  
        LazySingleton instance2 = LazySingleton.getInstance();  
        System.out.println(instance1 == instance2);  
    }  
  
}

3.懒汉式单例(增加synchronized保证线程安全)

定义:被外部类调用时才创建实例,利用synchronized保证线程安全

优点:线程安全;节约内存

缺点:synchronized关键字导致性能低下,在高并发场景下

package com.design.pattern.test03;  
  
public class LazySingleton {  
  
    private static LazySingleton singleton;  

    private LazySingleton() {  

    }  

    public static synchronized LazySingleton getInstance() {  
        if (singleton == null) {  
            singleton = new LazySingleton();  
        }  
        return singleton;  
    }  
  
}
package com.design.pattern.test03;  
  
/*  
在test02的基础上加上synchronized关键字  
缺点:synchronized关键字导致性能低下,在高并发场景下  
  
*/  
public class Test03懒汉式单例 {  
  
    public static void main(String[] args) {  
        LazySingleton instance1 = LazySingleton.getInstance();  
        LazySingleton instance2 = LazySingleton.getInstance();  
        System.out.println(instance1 == instance2);  
    }  
  
}

4.懒汉式单例(双重校验锁DCL)

定义:被外部类调用时才创建实例,利用DCL保证线程安全

优点:线程安全;节约内存

缺点:可读性不高,代码不够优雅

package com.design.pattern.test04;  
  
public class LazySingleton {  
    //volatile:内存可见性+防止指令重排
    private static volatile LazySingleton singleton;  

    private LazySingleton() {  

    }  

    public static LazySingleton getInstance() {  
        if (singleton == null) {  
            synchronized (LazySingleton.class) {  
                if (singleton == null) {  
                    LazySingleton temp = new LazySingleton();  
                    singleton = temp;  
                }  
            }  
        }  
        return singleton;  
    }  
  
}
package com.design.pattern.test04;  
  
/*  
双重校验锁DCL  
性能提高了,线程安全了。  
但是可读性不高,代码不够优雅,  
*/  
public class Test04懒汉式单例DCL {  
  
    public static void main(String[] args) {  
        LazySingleton instance1 = LazySingleton.getInstance();  
        LazySingleton instance2 = LazySingleton.getInstance();  
        System.out.println(instance1 == instance2);  
    }  
  
}

5.静态内部类单例

定义:利用静态内部类保证单例

优点:写法优雅,利用了java本身的语法特点,性能高,避免了内存浪费

缺点:能够被反射破坏

package com.design.pattern.test05;  
/**  
* 当getInstance方法第一次被调用的时候,它第一次读取LazyHolder.instance,导致LazyHolder类得到初始化;
* 而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,  
* 因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性  
*/
public class LazyStaticInnerClassSingleton {  
  
    private LazyStaticInnerClassSingleton() {  
    }  

    public static LazyStaticInnerClassSingleton getInstance() {  
        return LazyHolder.INSTANCE;  
    }  

    private static class LazyHolder {  

        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();  
    }  
  
}
package com.design.pattern.test05;  
  
/*  
静态内部类单例写法  
优点:写法优雅,利用了java本身的语法特点,性能高,避免了内存浪费  
缺点:能够被反射破坏  
*/  
public class Test05静态内部类单例 {  
  
    public static void main(String[] args) {  
        LazyStaticInnerClassSingleton instance1 = LazyStaticInnerClassSingleton.getInstance();  
        LazyStaticInnerClassSingleton instance2 = LazyStaticInnerClassSingleton.getInstance();  
        System.out.println(instance1 == instance2);  
    }  
  
}

6.静态内部类单例,防止被反射破坏单例

定义:利用静态内部类保证单例,可以防止被反射破坏

优点:写法优雅,利用了java本身的语法特点,性能高,避免了内存浪费

缺点:可以防止被反射破坏

package com.design.pattern.test06;  
  
public class LazyStaticInnerClassSingleton {  

    private LazyStaticInnerClassSingleton() {  
        if (LazyHolder.INSTANCE != null) {  
            throw new RuntimeException("防止反射破坏单例");  
        }  
    }  

    public static LazyStaticInnerClassSingleton getInstance() {  
        return LazyHolder.INSTANCE;  
    }  

    private static class LazyHolder {  

        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();  
    }  
  
}

package com.design.pattern.test06;  
  
  
/*  
静态内部类单例写法  
优点:写法优雅,利用了java本身的语法特点,性能高,避免了内存浪费  
防止被反射破坏  
*/  
public class Test06静态内部类单例防止反射破坏 {  
  
    public static void main(String[] args) {  
        LazyStaticInnerClassSingleton instance1 = LazyStaticInnerClassSingleton.getInstance();  
        LazyStaticInnerClassSingleton instance2 = LazyStaticInnerClassSingleton.getInstance();  
        System.out.println(instance1 == instance2);  
    }  
  
}
package com.design.pattern.test06;  
  
import java.lang.reflect.Constructor;  
  
// 尝试反射破坏单例  
public class ReflectTest {  
  
    public static void main(String[] args) {  
        try {  
            Class<?> clazz = LazyStaticInnerClassSingleton.class;  
            Constructor c = clazz.getDeclaredConstructor(null);  
            c.setAccessible(true);  
            Object instance = c.newInstance();  
            System.out.println(instance);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
}

7.注册式单例

定义:将每一个实例都缓存到统一的容器中,使用唯一标识获取实例

优点

缺点

package com.design.pattern.test07;  
  
public enum EnumSingleton {  
    INSTANCE;  

    public Object getData() {  
        return data;  
    }  

    public void setData(Object data) {  
        this.data = data;  
    }  

    private Object data;  


    public static EnumSingleton getInstance() {  
        return INSTANCE;  
    }  
  
}
package com.design.pattern.test07;  
  
import java.lang.reflect.Constructor;  
  
/*  
注册式单例  
将每一个实例都缓存到统一的容器中,使用唯一标识获取实例  
*/  
public class Test07注册式单例 {  
  
    public static void main(String[] args) {  
        EnumSingleton instance = EnumSingleton.getInstance();  
        instance.setData(new Object());  
        try {  
            Class clazz = EnumSingleton.class;  
            Constructor c = clazz.getDeclaredConstructor();  
            Object o = c.newInstance();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
}

8.容器氏单例

定义:利用Map的key不重复存储单例对象

优点

缺点

package com.design.pattern.test08;  
  
import java.util.Map;  
import java.util.concurrent.ConcurrentHashMap;  
  
public class ContainerSingleton {  
  
    private ContainerSingleton() {  
    }  

    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();  

    public static Object getInstance(String className) {  
        Object instance = null;  
        if (!ioc.containsKey(className)) {  
            try {  
                instance = Class.forName(className).newInstance();  
                ioc.put(className, instance);  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            return instance;  
        } else {  
            return ioc.get(className);  
        }  
    }  
}
package com.design.pattern.test08;  
  
/*  
容器氏单例,线程安全解决办法:加锁,锁住(IOC)容器  
* */  
public class Test08容器式单例 {  
  
    public static void main(String[] args) {  
        Object instance1 = ContainerSingleton.getInstance("com.design.pattern.test08.ContainerSingleton");  
        Object instance2 = ContainerSingleton.getInstance("com.design.pattern.test08.ContainerSingleton");  
        System.out.println(instance1 == instance2);  
    }  
  
}

9.持久化序列化单例

定义:将对象序列化存储到文件中,再读取这个文件反序列化生成该对象

优点

缺点

package com.design.pattern.test09;  
  
import java.io.Serializable;  
  
public class SerializableSingleton implements Serializable {  
    //序列化  
    //把内存中的对象的状态转化为字节码的形式  
    //把字节码通过IO输出流,写到磁盘!  
    //永久的保存下来  
    public final static SerializableSingleton INSTANCE = new SerializableSingleton();  

    private SerializableSingleton() {  
    }  

    public static SerializableSingleton getInstance() {  
        return INSTANCE;  
    }  

    // 反序列化的核心  
    private Object readResolve() {  
        return INSTANCE;  
    }  
  
}
package com.design.pattern.test09;  
  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
  
/*  
  
* */  
public class Test09持久化序列化单例 {  
  
    public static void main(String[] args) {  
        try {  
            SerializableSingleton s2 = SerializableSingleton.getInstance();  
            {  
                FileOutputStream fos = new FileOutputStream("filename");  
                ObjectOutputStream oos = new ObjectOutputStream(fos);  
                oos.writeObject(s2);  
                oos.flush();  
                oos.close();  
            }  

            SerializableSingleton s1 = null;  
            {  
                FileInputStream fis = new FileInputStream("filename");  
                ObjectInputStream ois = new ObjectInputStream(fis);  
                s1 = (SerializableSingleton) ois.readObject();  
                ois.close();  
            }  

            System.out.println(s1 == s2);  
        } catch (Exception e) {  
        e.printStackTrace();  
        }  
    }  
  
}

10.ThreadLocal单例

定义:保证一个线程内是单例的

优点

缺点

package com.design.pattern.test10;  
  
public class ThreadLocalSingleton {  
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =  
    new ThreadLocal<ThreadLocalSingleton>() {  
        @Override  
        protected ThreadLocalSingleton initialValue() {  
            return new ThreadLocalSingleton();  
        }  
    };  

    private ThreadLocalSingleton() {  
    }  

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


public class Test10ThreadLocal单例 {  
  
    public static void main(String[] args) {  
        System.out.println(ThreadLocalSingleton.getInstance());  
        System.out.println(ThreadLocalSingleton.getInstance());  
        System.out.println(ThreadLocalSingleton.getInstance());  
    }  
}

总结

学习单例模式的知识重点总结

  1. 私有化构造器
  2. 保证线程安全
  3. 延迟加载
  4. 防止序列化和反序列化破坏单例
  5. 防御反射攻击单例

下一篇

# 一天一种JAVA设计模式之二:工厂模式