有关电商网站开发的参考文献,网站广告,怎么做自己的视频网站,交换友情链接吧单例模式五种写法
单例模式有五种写法#xff1a;饿汉、懒汉、双重检验锁、静态内部类、枚举.
单例模式属于设计模式中的创建型模式
一、单例模式应用场景 windows的task manager(任务管理器)就是很典型的单例模式; windows的recycle bin(回收站)也是典型的单例应用#…单例模式五种写法
单例模式有五种写法饿汉、懒汉、双重检验锁、静态内部类、枚举.
单例模式属于设计模式中的创建型模式
一、单例模式应用场景 windows的task manager(任务管理器)就是很典型的单例模式; windows的recycle bin(回收站)也是典型的单例应用在整个系统运行过程中回收站一直维护仅有的一个实例; 项目中读取配置文件的类一般也只有一个对象没有必要每次使用配置文件数据每次new一个对象去读取; 应用程序的日志应用一般都可用单例模式实现这一般是由于共享的日志文件一直处于打开状态因为只能有一个实例去操作否则内容不好追加; 数据库连接池的设计一般也是采用单例模式因为数据库连接是一种数据库资源; 在spring中每个bean默认就是单例的这样做的优点是spring容器可以管理; 在servlet编程中每个servlet也是单例; 在spring mvc框架控制器对象也是单例; 单例模式算是设计模式中最容易理解也是最容易手写代码的模式了。但是其中的坑却不少所以也常作为面试题来考本文主要对几种单例写法的整理并分析其优缺点。 二、合格的单例模式要求
合格的单例模式的实现至少要保证以下三点
1. 实现单例功能
2. 延迟加载
3. 并发时不出错。 三、五种单例模式的实现
我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。
SingleObject 类提供了一个静态方法供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。 1. 饿汉式
这种方式非常简单因为单例的实例被声明成 static 和 final 变量了在第一次加载类到内存中时就会初始化所以创建实例本身是线程安全的
public class Singleton {private static final Singleton instance new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}饿汉模式的缺点可能在还不需要此实例的时候就已经把实例创建出来了没起到 lazy loading 效果空间换时间:浪费内存。饿汉模式的优点实现简单线程安全调用效率高。
2. 懒汉式
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance null) {instance new Singleton();}return instance;}
}懒汉模式的优点弥补了饿汉模式的缺点起到了 lazy loading 的效果时间换空间:节约内存。懒汉模式的缺点多线程并发时有线程安全问题有可能创建多个实例也就是说在多线程下不能正常工作。
3. 双重检验锁
双重检验锁模式double checked locking pattern是一种使用同步块加锁的方法。
称其为双重检查锁因为会有两次检查 instance null一次是在同步块外一次是在同步块内。
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance null) { //第一次检查,提高性能,避免所有的线程都去加锁和释放锁synchronized (Singleton.class) {if (instance null) { //第二次检查,确保只有一个线程创建实例instance new Singleton();}}}return instance;}
}为什么在同步块内还要再检验一次因为可能会有多个线程一起进入同步块外的 if如果在同步块内不进行二次检验的话就会生成多个实例了 这段代码看起来很完美很可惜它是有问题的。**主要在于instance new Singleton()这句这并非是一个原子操作事实上在 JVM 中这句话大概做了下面 3 件事情: **
(1) 给 instance 分配内存(2) 调用 Singleton 的构造函数来初始化成员变量(3) 将instance对象指向分配的内存空间执行完这步 instance 就为非 null 了但是在 JVM 的即时编译器(JIT)中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的最终的执行顺序可能是 1-2-3 也可能是 1-3-2。 如果是后者则在 3 执行完毕、2 未执行之前被线程二抢占了这时 instance 已经是非 null 了但却没有初始化所以线程二会直接返回 instance然后使用然后顺理成章地报错。
解决办法: 我们只需要将 instance 变量声明成 volatile 修饰就可以了其作用是防止指令重排序
public class Singleton {private volatile static Singleton instance; //声明成 volatileprivate Singleton (){}public static Singleton getInstance() {if (instance null) { synchronized (Singleton.class) {if (instance null) { instance new Singleton();}}}return instance;}
}使用 volatile 的主要原因是其另一个特性禁止指令重排序优化。也就是说在 volatile 变量的赋值操作后面会有一个内存屏障生成的汇编代码上读操作不会被重排序到内存屏障之前。比如上面的例子取操作必须在执行完 1-2-3 之后或者 1-3-2 之后不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作 双校验懒汉模式的优点弥补了懒汉模式的缺点防止了并发问题。双校验懒汉模式的缺点因为涉及到锁因此性能有损耗代码变得更复杂。
4. 静态内部类-登记式
静态内部类(static nested class)
使用静态内部类的方式也是《Effective Java》上所推荐的。
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; }
}懒汉式的变种: 静态内部类 空间换时间 首先静态内部类不会随着外部类的加载而加载 ,只有静态内部类的静态成员被调用时才会进行加载也就是初始化 ,这个就是调用静态内部类的静态成员,然后在初始化的过程中new一个对象INSTANCE 这种写法仍然使用JVM本身机制保证了线程安全问题由于 SingletonHolder 是私有的除了 getInstance() 之外没有办法访问它因此它是懒汉式的同时读取实例的时候不会进行同步没有性能缺陷
静态内部类单例的优点线程安全支持延迟加载获取singleton对象时不需要加锁使用方便。静态内部类单例的缺点会增加类的数量在第一次加载时可能会略微增加启动时间。
5. 枚举
用枚举写单例非常简单这也是它最大的优点。下面这段代码就是声明枚举实例的通常做法
public enum EasySingleton{INSTANCE;
}我们可以通过EasySingleton.INSTANCE来访问实例这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的所以不需要担心double checked locking而且还能防止反序列化导致重新创建新的对象。
枚举单例优点写法简单实现效率高因为枚举本身就是单例由JVM从根本上提供保障避免通过反射和反序列化的漏洞
枚举单例缺点没有延迟加载枚举实例在编译时就已经创建无法在运行时延迟加载
四、总结
一般来说单例模式有五种写法饿汉、懒汉、双重检验锁、静态内部类、枚举
一般情况下不建议使用第 2 种懒汉方式建议使用第 1 种饿汉方式。只有在要明确实现 lazy loading 效果时才会使用第 4 种静态内部类登记方式。如果涉及到反序列化创建对象时可以尝试使用第 5 种枚举方式。如果有其他特殊的需求可以考虑使用第 3 种双检锁方式。
单例类的特点
(1)单例类确保自己只有一个实例(2)单例类必须自己创建自己的实例(3)单例类必须为其他对象提供唯一的实例。单例类的优点
(1) 控制资源的使用通过线程同步来控制资源的并发访问。(2) 控制实例的产生数量达到节约资源的目的。(3) 作为通信的媒介数据共享。他可以在不建立直接关联的条件下让多个不相关的两个线程或者多个进程之间实现通信。单例模式的主要缺点如下
(1) 由于单例模式中没有抽象层因此单例类的扩展有很大的困难。(2) 单例类的职责过重在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色
提供了工厂方法同时又充当了产品角色包含一些业务方法将产品的创建和产品的本身的功能融合到一起。