相亲网站拉人做基金,电子商务网站建设的试卷,新郑做网站优化,网络广告网站怎么做单例模式 单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时#xff0c;为了防止频繁地创建对象使得内存飙升#xff0c;单例模式可以让程序仅在内存中创建一个对象#xff0c;让所有需要调用的地方都共享这一单例对象。 单…单例模式 单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时为了防止频繁地创建对象使得内存飙升单例模式可以让程序仅在内存中创建一个对象让所有需要调用的地方都共享这一单例对象。 单例模式的类型
单例模式有两种类型
懒汉式在真正需要使用对象时才去创建该单例类对象饿汉式在类加载时已经创建好该单例对象等待被程序使用 懒汉式
懒汉式创建对象的方法是在程序使用对象前先判断该对象是否已经实例化判空若已实例化直接返回该类对象。否则则先执行实例化操作。
public class Singleton {private static Singleton singleton;private Singleton(){}public static Singleton getInstance() {if (singleton null) {singleton new Singleton();}return singleton;}}
饿汉式
饿汉式在类加载时已经创建好该对象在程序调用时直接返回该单例对象即可即我们在编码时就已经指明了要马上创建这个对象不需要等到被调用时再去创建。
public class Singleton{private static final Singleton singleton new Singleton();private Singleton(){}public static Singleton getInstance() {return singleton;}
} 注意上面的代码在第3行已经实例化好了一个Singleton对象在内存中不会有多个Singleton对象实例存在 类在加载时会在堆内存中创建一个Singleton对象当类被卸载时Singleton对象也随之消亡了。 线程安全
懒汉式和饿汉式谁是线程安全的
饿汉是懒汉不是。
原因我们一个一个看 这个方法其实是存在问题的试想一下如果两个线程同时判断singleton为空那么它们都会去实例化一个Singleton对象这就变成双例了。所以我们要解决的是线程安全问题。
原子性用synchronized来保证if是判断再赋值的不是原子的
public static synchronized Singleton getInstance() {if (singleton null) {singleton new Singleton();}return singleton;
}
// 或者
public static Singleton getInstance() {synchronized(Singleton.class) { if (singleton null) {singleton new Singleton();}}return singleton;
}
这样就规避了两个线程同时创建Singleton对象的风险
原子性保证了还有重排序和可见性的问题。
使用volatile防止指令重排 创建一个对象在JVM中会经过三步
1为singleton分配内存空间
2初始化singleton对象
3将singleton指向分配好的内存空间
指令重排序是指JVM在保证最终结果正确的情况下可以不按照程序编码的顺序执行语句尽可能提高程序的性能
在这三步中第2、3步有可能会发生指令重排现象创建对象的顺序变为1-3-2会导致多个线程获取对象时有可能线程A创建对象的过程中执行了1、3步骤线程B判断singleton已经不为空获取到未初始化的singleton对象就会报NPE异常。文字较为晦涩可以看流程图 链接https://blog.csdn.net/weixin_41949328/article/details/107296517
使用volatile关键字修饰的变量可以保证其内存可见性即每一时刻线程读取到该变量的值都是内存中最新的那个值线程每次操作该变量都需要先读取该变量。
public class Singleton {private static volatile Singleton singleton;private Singleton() {}public static synchronized Singleton getInstance() {if (singleton null) {singleton new Singleton();}return singleton;}
} 这个代码已经基本安全了接下来考虑效率的问题。 每次去获取对象都需要先获取锁并发性能非常地差极端情况下可能会出现卡顿现象。 如果没有实例化对象则加锁创建如果已经实例化了则不需要加锁直接获取实例 所以直接在方法上加锁的方式就被废掉了因为这种方式无论如何都需要先获取锁 public static Singleton getInstance() {if (singleton null) { // 线程A和线程B同时看到singleton null如果不为null则直接返回singletonsynchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化if (singleton null) { // 其中一个线程进入该分支另外一个线程则不会进入该分支singleton new Singleton();}}}return singleton;
} 代码已经完美地解决了并发安全性能低效问题 第2行代码如果singleton不为空则直接返回对象不需要获取锁而如果多个线程发现singleton为空则进入分支 第3行代码多个线程尝试争抢同一个锁只有一个线程争抢成功第一个获取到锁的线程会再次判断singleton是否为空因为singleton有可能已经被之前的线程实例化 其它之后获取到锁的线程在执行到第4行校验代码发现singleton已经不为空了则不会再new一个对象直接返回对象即可 之后所有进入该方法的线程都不会去获取锁在第一次判断singleton对象时已经不为空了 枚举实现
public enum Singleton {INSTANCE;public void doSomething() {System.out.println(这是枚举类型的单例模式);}
}
使用枚举实现单例模式的优势在哪里
我们从最直观的地方入手第一眼看到这几行代码就会感觉到“少”没错就是少虽然这优势有些牵强但写的代码越少越不容易出错。
优势1代码对比饿汉式与懒汉式来说更加地简洁
其次既然是实现单例模式那这种写法必定满足单例模式的要求而且使用枚举实现时没有做任何额外的处理。
优势2它不需要做任何额外的操作去保证对象单一性与线程安全性
我写了一段测试代码放在下面这一段代码可以证明程序启动时仅会创建一个 Singleton 对象且是线程安全的。
我们可以简单地理解枚举实现单例的过程在程序启动时会调用Singleton的空参构造器实例化好一个Singleton对象赋给INSTANCE之后再也不会实例化
总结
1单例模式常见的写法有两种懒汉式、饿汉式
2懒汉式在需要用到对象时才实例化对象正确的实现方式是Double Check Lock解决了并发安全和性能低下问题
3饿汉式在类加载时已经创建好该单例对象在获取单例对象时直接返回对象即可不会存在并发安全和性能问题。
4在开发中如果对内存要求非常高那么使用懒汉式写法可以在特定时候才创建该对象
5如果对内存要求不高使用饿汉式写法因为简单不易出错且没有任何并发安全和性能问题
6为了防止多线程环境下因为指令重排序导致变量报NPE需要在单例对象上添加volatile关键字防止指令重排序
7最优雅的实现方式是使用枚举其代码精简没有线程安全问题且 Enum 类内部防止反射和反序列化时破坏单例。