目录
单例模式的5种实现方式:
1.饿汉式(Eager Initialization):
2.懒汉式(Lazy Initialization):
3.双重检查锁定(Double-Checked Locking):
4.静态内部类(Static Inner Class):
5.注册式单例(Registration Style Singleton):
三种破坏单例模式的方式:
1.反射
2.序列化和反序列化
3.原型模式调用clone()方法
使用场景:
涉及单例模式的JDK源码:
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
优点:
全局唯一性:单例模式确保在应用程序中只有一个实例,这对于共享资源、配置管理和维护全局状态非常有用。
懒加载:如果实例在第一次使用时才被创建,可以延迟实例化,从而节省资源。
全局访问点:单例提供了一个统一的入口点,使得可以轻松地访问单例实例,而不需要传递实例的引用。
节省内存:由于只有一个实例存在,因此节省了多个相同对象的内存。
避免竞态条件:在多线程环境中,单例模式可以通过加锁等机制来确保只有一个实例被创建,从而避免竞态条件。
缺点:
全局状态:单例模式引入了全局状态,这可能导致代码的复杂性和耦合性增加。全局状态可能会导致难以调试和理解的问题。
不适用于多线程:在多线程环境中,如果没有正确处理,单例模式可能导致性能问题或竞态条件。需要额外的措施来确保线程安全。
隐藏依赖:使用单例模式的类隐藏了其依赖关系,因为它们直接访问全局实例。这可能会使代码更难测试和维护。
不支持子类化:有时候,单例模式难以支持子类化,因为它将构造函数私有化,防止直接实例化子类。
单一职责原则:有时,将全局状态与其他功能耦合在一起可能会违反单一职责原则。
总之,单例模式是一个有用的设计模式,可以在需要确保只有一个实例存在的情况下使用。然而,开发人员应该谨慎使用,考虑到它可能引入的全局状态和复杂性。在多线程环境中,需要特别小心,以确保线程安全。
优点:
缺点:
public class HungrySingleton {
// 私有化构造方法:阻止通过构造方法实例化单例对象
private HungrySingleton(){}
// 私有属性:单例对象
private static HungrySingleton hungrySingleton = new HungrySingleton();
// 提供全局访问点
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
静态代码块的方式也属于类加载时候执行
public class StaticHungrySingleton {
private StaticHungrySingleton(){}
private static StaticHungrySingleton hungrySingleton;
static {
hungrySingleton = new StaticHungrySingleton();
}
public static StaticHungrySingleton getInstance(){
return hungrySingleton;
}
}
优点:
缺点:
a.线程不安全的懒汉式:
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySingleton = null;
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySimpleSingleton();
}
return lazySingleton;
}
}
多线程环境下不安全的原因:如果多个线程能够同时进入if(lazySingleton == null),并且此时lazySingleton为空,就会有多个线程执行lazySingleton = new LazySimpleSingleton();将导致多次实例化lazySingleton.
b.线程安全的懒汉式:
通过对getInstance()方法加锁,可以实现一个时间点只有一个线程能够进入该方法,避免多次实例化的问题。
public class LazySynchronizedSingleton {
private static LazySynchronizedSingleton lazySingleton = null;
private LazySynchronizedSingleton(){}
public static synchronized LazySynchronizedSingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySynchronizedSingleton();
}
return lazySingleton;
}
}
优点:
缺点:
方法加上synchronized锁解决了线程安全问题,但是在线程数量比较多的情况下,大量线程会阻塞在方法外部,导致程序性能下降。所以为了兼顾性能和线程安全问题,我们可以通过双重检查锁的方式创建懒汉式单例。
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazySingleton = null;// volatile禁止指令重排序
private LazyDoubleCheckSingleton(){}
public static synchronized LazyDoubleCheckSingleton getInstance(){
if(lazySingleton == null){ // #1
// 如果lazySingleton为空才加锁进行初始化
synchronized(LazyDoubleCheckSingleton.class){ // #2
if (lazySingleton == null){ // #3
/* 如果没有第二次判断就会发生:
1.A执行完#1
2.在#1和#2之间发生了线程切换,切换到了B
3.线程B执行了#2,获取到了锁,并初始化了lazySingleton
4.切换到了线程A,线程A执行了#2,获取到了锁,并初始化了lazySingleton
这样就导致初始化了两次,所以必须进行二次校验
*/
lazySingleton = new LazyDoubleCheckSingleton(); // #4
}
}
}
return lazySingleton;
}
}
volatile关键字的作用是防止多线程环境下#3和#4发生重排序,可能导致NPE(空指针异常)。
优点:
缺点:
我们知道用到synchronized关键字总归要上锁,对程序性能还是存在一定影响的。从类的初始化角度来考虑,可以采用静态内部类的方式。
/*
懒汉式单例-静态内部类只会在调用instance的时候初始化,延迟初始化的同时虚拟机提供了对线程安全的支持。
*/
public class LazyStaticInnerSingleton {
private LazyStaticInnerSingleton(){}
public static synchronized LazyStaticInnerSingleton getInstance(){
return LazySingletonHolder.lazySingleton;
}
private static class LazySingletonHolder {
private static LazyStaticInnerSingleton lazySingleton = new LazyStaticInnerSingleton();
}
}
a.枚举(Enum):
优点:
缺点:
/*
Effective Java 推荐的方式:使用jvm封装的枚举类通过注册的形式获取单例,实际上也算一种饿汉式,但是性能损耗已经被jvm处理过,相比于个人进行优化要好得多。
*/
public enum LazyEnumSingleton {
INSTANCE;
// 使用时直接调用LazyEnumSingleton.INSTANCE.sayHello()实现单例。
public void sayHello(){
System.out.println("Hello world !");
}
}
b.容器实现(Container Managed Singleton):
优点:
缺点:
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map ioc = new ConcurrentHashMap<>();
public static Object getBean(String className){
synchronized (ioc){
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
c.本地线程单例(ThreadLocal):
优点:
缺点:
/*
ThreadLocal也算是一种注册式的单例实现:线程间隔离的ThreadLocal其实是存储在一个TreadLocalMap中,初始化的时候就put到了Map里,有就直接拿出来用。
ThreadLocal采用空间换时间的方式为每一个线程都提供了一份变量,同时访问而不影响。同步机制是采取时间换空间的方式排队访问。
*/
public class ThreadLocalSingleton {
private ThreadLocalSingleton(){}
private static final ThreadLocal tlSingleton = new ThreadLocal(){
@Override
protected ThreadLocalSingleton initialValue(){
return new ThreadLocalSingleton();
}
};
public static ThreadLocalSingleton getInstance(){
return tlSingleton.get();
}
}
通过反射拿到私有的构造方法,设置setAccess()为true更改访问权限为public暴力初始化也能达到破坏单例的目的。
反射破坏单例的解决方案:在构造方法中添加限制条件,抛出异常阻止初始化
public class DestroySingletonReflex {
private DestroySingletonReflex(){
// 反射破坏单例的解决方案:在构造方法中添加判断,抛出异常
// if (DestroySingletonReflexHolder.destroySingletonReflex != null){
// throw new RuntimeException("不允许创建多个实例!");
// }
}
private static class DestroySingletonReflexHolder{
private static DestroySingletonReflex destroySingletonReflex = new DestroySingletonReflex();
}
public static DestroySingletonReflex getInstance(){
return DestroySingletonReflexHolder.destroySingletonReflex;
}
/*
通过反射破坏单例
*/
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 通过反射创建实例
Class> clazz = DestroySingletonReflex.class;
// 通过反射获取构造方法
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
// 暴力初始化
Object o1 = c.newInstance();
Object o2 = c.newInstance();
System.out.println(o1);
System.out.println(o2);
System.out.println("是否相等:" + o1==o2);
}
}
当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时 再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。
反序列化破坏单例的本质是ObjectInputStream类的readObject()通过反射方式调用NewInstance()初始化实例。
反序列化破坏单例的解决方案:添加readResolve方法,改变checkResolve()结果阻止反射机制初始化实例。
public class DestroySingletonSerialize implements Serializable{
private DestroySingletonSerialize(){
}
private static class DestroySingletonReflexHolder{
private static DestroySingletonSerialize destroySingletonReflex = new DestroySingletonSerialize();
}
public static DestroySingletonSerialize getInstance(){
return DestroySingletonReflexHolder.destroySingletonReflex;
}
public Object readResolve(){
return getInstance();
}
/*
通过序列化破坏单例
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
DestroySingletonSerialize d1 = null;
DestroySingletonSerialize d2 = DestroySingletonSerialize.getInstance();
// 通过FileOutPutStream + ObjectOutPutStream将对象d2写到文件里进行序列化
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("tempFile"));
outputStream.writeObject(d2);
outputStream.flush();
outputStream.close();
// 通过FileInputStream和ObjectInputStream将文件中的对象读到d1中
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("tempFile"));
d1 =(DestroySingletonSerialize) objectInputStream.readObject();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}
}
原型破坏单例:实现Cloneable的单例类调用clone()复制实例是通过copy内存实现的,没有使用构造方法。
原型破坏单例的解决方案:不实现Cloneable接口。
public class DestorySingletonPrototype implements Cloneable{
private DestorySingletonPrototype(){}
private static class DestorySingletonPrototypeHolder{
private static DestorySingletonPrototype singletonPrototype = new DestorySingletonPrototype();
}
public static DestorySingletonPrototype getInstance(){
return DestorySingletonPrototypeHolder.singletonPrototype;
}
public static void main(String[] args) throws CloneNotSupportedException {
DestorySingletonPrototype singletonPrototype = DestorySingletonPrototype.getInstance();
DestorySingletonPrototype singletonPrototype1 = (DestorySingletonPrototype)singletonPrototype.clone();
System.out.println(singletonPrototype1);
System.out.println(singletonPrototype);
System.out.println(singletonPrototype1 == singletonPrototype);
}
}