智能科技网站模板下载地址,wordpress近义词搜索,WordPress仿站助手,中国工程项目网站单例模式 单例模式 (Singleton) (重点)1) 为什么要使用单例2) 如何实现一个单例2.a) 饿汉式2.b) 懒汉式2.c) 双重检查锁2.d) 静态内部类2.e) 枚举类2.f) 反射入侵2.g) 序列化与反序列化安全 3) 单例存在的问题3.a) 无法支持面向对象编程 单例模式 (Singleton) (重点)
一个类只… 单例模式 单例模式 (Singleton) (重点)1) 为什么要使用单例2) 如何实现一个单例2.a) 饿汉式2.b) 懒汉式2.c) 双重检查锁2.d) 静态内部类2.e) 枚举类2.f) 反射入侵2.g) 序列化与反序列化安全 3) 单例存在的问题3.a) 无法支持面向对象编程 单例模式 (Singleton) (重点)
一个类只允许创建一个对象或者实例那这个类就是一个单例类
1) 为什么要使用单例
1.表示全局唯一
如果有些数据在系统中应该且只能保存一份那就应该设计为单例类:
配置类在系统中我们只有一个配置文件当配置文件被加载到内存之后应该被映射为一个唯一的【配置实例】全局计数器我们使用一个全局的计数器进行数据统计、生成全局递增ID等功能。若计数器不唯一很有可能产生统计无效ID重复等
2.处理资源访问冲突
如果使用单个实例输出日志锁【this】即可。
如果要保证JVM级别防止日志文件访问冲突锁【class】即可。
如果要保证集群服务级别的防止日志文件访问冲突加分布式锁即可
2) 如何实现一个单例
常见的单例设计模式有如下五种写法在编写单例代码的时候要注意以下几点:
1.构造器需要私有化2.暴露一个公共的获取单例对象的接口3.是否支持懒加载延迟加载4.是否线程安全
2.a) 饿汉式
在类加载的时候instance 静态实例就已经创建并初始化好了所以instance 实例的创建过程是线程安全的
/*** 饿汉式单例的实现* - 不支持懒加载* - jvm保证线程安全*/
public class EagerSingleton {/*** 当启动程序的时候就创建这个实例*/// 1.持有一个jvm全局唯一的实例private static final EagerSingleton instance new EagerSingleton();// 2.为了避免别人随意的创建需要私有化构造器private EagerSingleton() {}// 3.暴露一个方法用来获取实例public static EagerSingleton getInstance() {return instance;}
}2.b) 懒汉式
懒汉式相对于饿汉式的优势是支持延迟加载具体的代码实现如下所示
支持延迟加载
/*** 懒汉式单例的实现* - 支持懒加载*/
public class LazySingleton {/*** 当需要使用这个实例的时候再创建这个实例*/// 1.持有一个jvm全局唯一的实例private static LazySingleton instance;// 2.为了避免别人随意的创建需要私有化构造器private LazySingleton() {}// 3.暴露一个方法用来获取实例// - 懒加载-线程不安全因为当面对大量并发请求时有可能会有超过一个线程同时执行此方法是无法保证其单例的特点// - 加锁使用 synchronized(对.class加锁) 但是方法上加锁会极大的降低获取单例对象的并发度public static LazySingleton getInstance() {if (instance null) {instance new LazySingleton();}return instance;}
}2.c) 双重检查锁
饿汉式不支持延迟加载懒汉式有性能问题不支持高并发。既支持延迟加载、又支持高并发的单例实现方式也就是双重检测锁
/*** 双重检查锁单例的实现*/
public class DoubleCheckLockSingleton {// 1.持有一个jvm全局唯一的实例// - 因为创建对象不是一个原子性操作即使使用双重检查锁也可能在创建过程中产生半初始化状态// - volatile 1.保证内存可见 2.保存有序性// - jdk1.9以上不加volatile也可以jvm内部处理有序性private static volatile DoubleCheckLockSingleton instance;// 2.为了避免别人随意的创建需要私有化构造器private DoubleCheckLockSingleton() {}// 3.暴露一个方法用来获取实例// - 第一次创建需要上锁一旦创建完成就不再需要上锁// - 事实上获取单例并没有线程安全的问题public static DoubleCheckLockSingleton getInstance() {if (instance null) {synchronized (DoubleCheckLockSingleton.class) {// 创建if (instance null) {instance new DoubleCheckLockSingleton();}}}return instance;}
}2.d) 静态内部类
比双重检测更加简单的实现方法那就是利用 Java 的静态内部类。它有点类似饿汉式但又能做到了延迟加载。
当外部类 InnerSingleton()被加载的时候并不会创建 InnerSingleton的实例对象。只有当调用 getInstance() 方法时InnerSingletonHolder 才会被加载这个时候才会创建 instance实例。
/*** 静态内部类的方式实现单例*/
public class InnerSingleton {// 1.私有化构造器private InnerSingleton() {}// 2.提供一个方法获取单例对象public InnerSingleton getInstance() {return InnerSingletonHolder.instance;}// 3.定义内部类来持有实例// - 特性类加载的时机 -- 一个类会在第一次使用的时候被加载// - 实例会在内部类加载调用getInstance()方法之后会创建private static class InnerSingletonHolder {private static final InnerSingleton instance new InnerSingleton();}}2.e) 枚举类
基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性保证了实例创建的线程安全性和实例的唯一性。
/*** 枚举累加器*/
public enum GlobalCounter {// 这个INSTANCE是一个单例// 对于枚举类。任何一个枚举项就是一个单例// 本质上就是 static final GlobalCounter instance new GlobalCounter()INSTANCE;private AtomicLong atomicLong new AtomicLong(0);public Long getNumber() {return atomicLong.getAndIncrement();}
}2.f) 反射入侵
事实上我们想要阻止其他人构造实例仅仅私有化构造器还是不够的因为我们还可以使用反射获取私有构造器进行构造当然使用枚举的方式是可以解决这个问题的对于其他的书写方案我们通过下边的方式解决
// 反射代码
ClassDoubleCheckLockSingleton instance DoubleCheckLockSingleton.class;
ConstructorDoubleCheckLockSingleton constructor instance.getDeclaredConstructor();
constructor.setAccessible(true);boolean flag DoubleCheckLockSingleton.getInstance() constructor.newInstance();
log.info(flag - {},flag);/*** 单例的防止反射入侵的代码实现*/
public class ReflectSingleton {/*** 可以使用反射获取私有构造器进行构造*/private static volatile ReflectSingleton instance;// 为了避免别人随意的创建需要私有化构造器private ReflectSingleton() {// 升级版本 -- 不要让人使用反射创建if (instance ! null) {throw new RuntimeException(该对象是单例无法创建多个);}}public static ReflectSingleton getInstance() {if (instance null) {synchronized (ReflectSingleton.class) {// 创建if (instance null) {instance new ReflectSingleton();}}}return instance;}
}2.g) 序列化与反序列化安全
事实上到目前为止我们的单例依然是有漏洞的
/*** 通过序列化*/
Test
public void testSerialize() throws Exception {// 获取单例并序列化SerializableSingleton instance SerializableSingleton.getInstance();FileOutputStream fout new FileOutputStream(F://singleton.txt);ObjectOutputStream out new ObjectOutputStream(fout);out.writeObject(instance);// 将实例反序列化出来FileInputStream fin new FileInputStream(F://singleton.txt);ObjectInputStream in new ObjectInputStream(fin);Object o in.readObject();log.info(是同一个实例吗 {}, o instance); // 是同一个实例吗 false
}在进行反序列化时会尝试执行readResolve方法并将返回值作为反序列化的结果而不会克隆一个新的实例保证jvm中仅仅有一个实例存在
public class Singleton implements Serializable {// 省略其他的内容public static Singleton getInstance() {}// 需要加这么一个方法public Object readResolve(){return singleton;}
}3) 单例存在的问题
在项目中使用单例都是用它来表示一些全局唯一类比如配置信息类、连接池类、ID 生成器类。单例模式书写简洁、使用方便在代码中我们不需要创建对象。但是这种使用方法有点类似硬编码hard code会带来诸多问题所以我们一般会使用spring的单例容器作为替代方案。
3.a) 无法支持面向对象编程
OOP 的三大特性是封装、继承、多态。单例将构造私有化直接导致的结果就是他无法成为其他类的父类这就相当于直接放弃了继承和多态的特性也就相当于损失了可以应对未来需求变化的扩展性以后一旦有扩展需求比如写一个类似的具有绝大部分相同功能的单例我们不得不新建一个十分【雷同】的单例。