脚本宝典收集整理的这篇文章主要介绍了波吉学设计模式——玩转单例模式,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供了一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的方法权限设置为PRivate,这样就不能用new操作符在类的外部产生类的对象了,但是类内部仍然可以产生该类的对象,因为在类的外部开始还没发得到类的对象,只能调用该类的某个静态方法以返回类内部构建的对象,静态方法只能访问类中的静态成员变量,所以指向类内部产生的该类对象的变量也必须定义为静态的
保证一个类只有一个对象,并且提供一个访问该实例的全局访问点
单例模式只生成了一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其它依赖对象,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决,例如F1a;java.lang.Runtime,网站计数器,应用程序的日志应用,数据库连接池,读取配置文件的类,Application,Windows中的任务管理器和回收站
@AutoWrie
的依赖注入默认就是单例的线程安全
class Bank {
//1.私有化构造器,不允许外部可以调用
private Bank() {
}
//2.内部创建类的对象
//4.要求此对象必须声明为静态
private static Bank instance = new Bank();
//3.提供公共的方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}
public class Singletontest1 {
public static void main(String[] args) {
Bank bank = Bank.getInstance();
}
}
单例模式实现步骤:
这是最简单实现懒汉单例的实例,后续内容会对懒汉单例做出升级!在本次实例中线程不安全
/**
* 单例设计模式——懒汉
*
* @author ccy
* @version 1.0
* @date 2021/12/6 13:18
*/
class Order {
//1.私有化构造器,不允许外部可以调用
private Order() {
}
//2.声明当前类的对象
//4.要求此对象必须声明为静态
private static Order instance = null;
//3.提供公共的方法,返回类的对象
public static Order getInstance() {
if (instance == null) {
instance = new Order();
}
return instance;
}
}
标题写到这种情况的单例模式是线程不安全原因就在于在高并发的场景下会创建多个对象违背了单例模式只创建一次的情况
为了应对高并发下能够只创建一次对象的情况所以我们引入SynchROIzed
,但是采用synchronized
对方法加锁有很大的性能开销,因为当getInstance()
内部逻辑比较复杂的时候,在高并发条件下没获取到加锁方法执行权的线程,都得等到这个方法内的复杂逻辑执行完后才能执行,等待浪费时间,效率比较低
这种实现懒汉单例线程是安全的但是不采用它的原因就在于synchronized带来的效率低
class Order {
//1.私有化构造器,不允许外部可以调用
private Order() {
}
//2.声明当前类的对象
//4.要求此对象必须声明为静态
private static Order instance = null;
//3.提供公共的方法,返回类的对象
public synchronized static Order getInstance() {
if (instance == null) {
instance = new Order();
}
return instance;
}
}
为了满足以上需求,DCL双重检测锁机制的单例模式就出现了
下面是上述代码的运行顺序:
- 检测实例是否已经初始化创建,如果是则立即返回
- 获得锁
- 再次检测实例是否已经初始化创建成功,如果还没有则创建实例
/**
* 单例设计模式——懒汉
*
* @author ccy
* @version 1.0
* @date 2021/12/6 13:18
*/
class Order {
//1.私有化构造器,不允许外部可以调用
private Order() {
}
//2.声明当前类的对象
//4.要求此对象必须声明为静态
private static Order instance = null;
//3.提供公共的方法,返回类的对象
public static Order getInstance() {
if (instance == null) {
synchronized (Order.class) {
if (instance == null) {
instance = new Order();
}
}
}
return instance;
}
}
public class Singletontest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
DCL双重检测锁机制在逻辑上的确是趋近于完美了但是!!!由于指令重排的原因仍然有可能会创建多个对象,因为instance = new Order()
这行代码的执行逻辑是
由于指令重排(你可以把它理解成编译器为了优化代码而对实际写出的代码在机器中进行重新排序导致原本的代码执行顺序发生变化)的缘故会出现这样的情况
在多线程下指令重排给带来的问题就会被放大
执行顺序 | Thread1 | Thread2 |
---|---|---|
1 | 第一次检测, instance 为null | |
2 | 获取锁 | |
3 | 第二次检测, instance 为null | |
4 | 在堆中分配内存空间 | |
5 | instance 指向分配的内存空间 | |
6 | 第一次检测,instance不为null | |
7 | 此时Thread 2对 instance的访问,访问到的是一个还未完成初始化的对象。所以在使用 instance 时可能会出错 | |
8 | 初始化 instance |
所以为了避免指令重排我们只需要对初始化对象加volatile
这一关键字即可,volatile
是可以预防指令重排
下面代码是正确的双重检测锁机制
/**
* 正确的双重检测锁机制
*
* @author ccy
* @version 1.0
* @date 2021/12/6 13:18
*/
class Order {
//1.私有化构造器,不允许外部可以调用
private Order() {
}
//2.声明当前类的对象
//4.要求此对象必须声明为静态
private volatile static Order instance = null; // 注意哦这里添加了关键字volatile为了防止指令重排
public static Order getInstance() {
if (instance == null) {
synchronized (Order.class) {
if (instance == null) {
instance = new Order();
}
}
}
return instance;
}
}
虽然这种单例模式线程安全虽然构造方法是私有的但是Java中反射可以破坏私有方法,我们仍然可以通过反射来获取对象
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName());
}
private volatile static Lazy lazy;
public static Lazy getInstance() {
if(lazy == null) {
synchronized (Lazy.class) {
if(lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws Exception {
Lazy instance1 = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
}
}
没办法了只能搬出杀手锏了枚举元素天生就是线程安全的单例,调用效率也高,只是无法延时加载!
main方法中是我尝试使用反射来破坏枚举单例
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance() {
return INSTANCE;
}
}
//尝试使用反射破坏枚举单例
class testSingle {
public static void main(String[] args) throws Exception {
EnumSingle instance = EnumSingle.getInstance();
// EnumSingle instance1 = EnumSingle.INSTANCE;
// System.out.println(instance == instance1);
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance == instance2);
}
}
虽然IDEA报错了但是错误并不是我们预计的错误
但从.class文件来看确实是存在无参构造的方法经过一系列手段后我们了解到实际上调用的是有参的构造方法
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
调整代码以后报错成了我们预计的
以上是脚本宝典为你收集整理的波吉学设计模式——玩转单例模式全部内容,希望文章能够帮你解决波吉学设计模式——玩转单例模式所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。