单例模式

单例模式指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
单例模式有 3 个特点:
单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点;

优点:
内存中只有一个实例,减少了内存开销;
可以避免对资源的多重占用;
设置全局访问点,严格控制访问。

缺点:
没有接口,拓展困难。

单例模式的主要角色有:
单例类:包含一个实例且能自行创建这个实例的类。
访问类:使用单例的类。

1.懒汉模式

懒汉模式下的单例写法是最简单的,但它是线程不安全的:

public class LazyMode {//定义一个静态实例private static LazyMode lazyMode=null;public LazyMode() {}public static LazyMode getInstance(){if(lazyMode==null){lazyMode=new LazyMode();}return lazyMode;}
}

2.同步锁单例模式

加上一个同步锁解决下线程安全问题:

//同步锁单例模式,加同步锁,解决线程安全
public class LazyMode2 {//定义一个静态实例private static LazyMode2 lazyMode=null;public LazyMode2() {}public static LazyMode2 getInstance(){synchronized (LazyMode2.class){//加上同步锁if(lazyMode==null){lazyMode=new LazyMode2();}}return lazyMode;}
}

但是这个同步锁锁的是整个类,比较消耗资源,并且即使运行内存中已经存在LazyMode2,调用其getInstance还是会上锁。

3.双重同步锁单例模式

我们再进行优化一下,看下面的例子,在同步锁上再加上一层判断,变成双重同步锁,当LazyMode3 实例创建好后,后续再调用其getInstance方法不会上锁。
要注意的是虽然弄了双重同步锁,但它可能还是线程不安全的。虽然不会出现多次初始化LazyMode3 实例的情况,但是由于指令重排的原因,某些线程可能会获取到空对象,后续对该对象的操作将触发空指针异常。要修复这个问题,只需要阻止指令重排即可,所以可以给LazyMode3 属性加上volatile关键字,来确保线程安全。
volatile关键字修饰的成员变量具有两大特性:保证了该成员变量在不同线程之间的可见性;禁止对该成员变量进行重排序,也就保证了其有序性。但是volatile修饰的成员变量并不具有原子性,在并发下对它的修改是线程不安全的

//懒汉模式,加上双重同步锁
public class LazyMode3 {//定义一个静态实例private volatile static LazyMode3 lazyMode=null;//加上volatile关键字阻止指令重排,通过volatile修饰的成员变量   会添加内存屏障来阻止JVM进行指令重排优化。public LazyMode3() {}public static LazyMode3 getInstance(){if(lazyMode==null) {//再加上一层判断,防止多次加锁synchronized (LazyMode3.class) {//加上同步锁if (lazyMode == null) {lazyMode = new LazyMode3();}}}return lazyMode;}
}

4.静态内部类单例模式

JVM在类的初始化阶段会加Class对象初始化同步锁,同步多个线程对该类的初始化操作;
静态内部类InnerClass的静态成员变量lazyStaticMode在方法区中只会有一个实例。
在Java规范中,当以下这些情况首次发生时,class类将会立刻被初始化:
1.class类型实例被创建;
2.class类中声明的静态方法被调用;
3.class类中的静态成员变量被赋值;
4.class类中的静态成员被使用(非常量);

//静态内部类单例模式
public class LazyStaticMode {public LazyStaticMode() {}public static class InnerClass{//在内部类中定义一个静态实例private static LazyStaticMode lazyStaticMode=new LazyStaticMode();}public static LazyStaticMode getInstance(){return InnerClass.lazyStaticMode;//通过内部类去获取实例}
}

5.饿汉单例模式

该模式是在类加载的时候就初始化:

//饿汉单例模式
public class HungaryMode {private static HungaryMode hungaryMode=new HungaryMode();//直接创建实例public HungaryMode(){}public static HungaryMode getInstance(){return hungaryMode;}
}

这种模式在类加载的时候就完成了初始化,所以并不存在线程安全性问题;但由于不是懒加载,饿汉模式不管需不需要用到实例都要去创建实例,如果创建了不使用,则会造成内存浪费。
而且这个模式容易被序列化和反射破坏,下面做个序列化和反射的破坏例子

6.序列化破坏单例模式

1.让前面的饿汉单例模式,,实现序列化接口

//序列化破坏单例模式,实现序列化接口
public class HungaryMode2 implements Serializable{private static final long serialVersionUID = -9086351862017233122L;private static HungaryMode2 hungaryMode=new HungaryMode2();public HungaryMode2(){}public static HungaryMode2 getInstance(){return hungaryMode;}
}

2.测试输出

public class Test {public static  void main(String[]args) throws IOException, ClassNotFoundException {HungaryMode2 hungaryMode2=HungaryMode2.getInstance();//对象输出ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("file"));outputStream.writeObject(hungaryMode2);//对象输入ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("file"));HungaryMode2 hungaryMode3 = (HungaryMode2) inputStream.readObject();System.out.println(hungaryMode2);System.out.println(hungaryMode3);System.out.println(hungaryMode2==hungaryMode3);}
}
//com.wfg.design.mode5.HungaryMode2@4a574795
//com.wfg.design.mode5.HungaryMode2@f6f4d33
//false

从输出可以看到,即使它是单例模式,也成功创建出了两个不一样的实例,单例遭到了破坏。
要让反序列化后的对象和序列化前的对象是同一个对象的话,可以在HungaryMode2 里加上readResolve方法:
这种方式反序列化过程内部还是会重新创建HungaryMode2 实例,但是因为HungaryMode2 类定义了readResolve方法(方法内部返回了hungaryMode对象引用),反序列化过程中会判断目标类是否定义了readResolve该方法,是的话则会通过反射调用该方法,所以最终获取到的还是HungaryMode2

//序列化破坏单例模式,实现序列化接口
public class HungaryMode2 implements Serializable{private static final long serialVersionUID = -9086351862017233122L;private static HungaryMode2 hungaryMode=new HungaryMode2();public HungaryMode2(){}public static HungaryMode2 getInstance(){return hungaryMode;}public Object readResolve(){//添加上readResolve方法return hungaryMode;}
}

测试输出

public class Test {public static  void main(String[]args) throws IOException, ClassNotFoundException {HungaryMode2 hungaryMode2=HungaryMode2.getInstance();//对象输出ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("file"));outputStream.writeObject(hungaryMode2);//对象输入ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("file"));HungaryMode2 hungaryMode3 = (HungaryMode2) inputStream.readObject();System.out.println(hungaryMode2);System.out.println(hungaryMode3);System.out.println(hungaryMode2==hungaryMode3);}
}
//com.wfg.design.mode5.HungaryMode2@4a574795
//com.wfg.design.mode5.HungaryMode2@4a574795
//true

7.反射破坏单例模式

public static  void main(String[]args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {HungaryMode2 hungaryMode2=HungaryMode2.getInstance();//通过反射创建了HungaryMode2实例Class<HungaryMode2> cs=HungaryMode2.class;//获取HungaryMode2里的构造Constructor<HungaryMode2> ct=cs.getConstructor();//打开构造权限ct.setAccessible(true);HungaryMode2 hungaryMode3=ct.newInstance();System.out.println(hungaryMode2);System.out.println(hungaryMode3);System.out.println(hungaryMode2==hungaryMode3);}//com.wfg.design.mode5.HungaryMode2@27716f4//com.wfg.design.mode5.HungaryMode2@8efb846//false

从上面输出可以看到,可以通过反射破坏了私有构造器权限,成功创建了新的实例。
对于这种情况,饿汉模式下的例子可以在构造器中添加判断逻辑来防御(懒汉模式的就没有办法了)

//序列化破坏单例模式,实现序列化接口
public class HungaryMode2 implements Serializable{private static final long serialVersionUID = -9086351862017233122L;private static HungaryMode2 hungaryMode=new HungaryMode2();public HungaryMode2(){if(hungaryMode==null){//通过在这里加上判断来阻止反射破坏throw new RuntimeException("不允许通过这个构造器来生成实例");}}public static HungaryMode2 getInstance(){return hungaryMode;}public Object readResolve(){return hungaryMode;}
}

再次模拟反射,发现抛出我们的自己定义的错误,阻止反射破坏实例

Exception in thread "main" java.lang.ExceptionInInitializerErrorat com.wfg.design.mode5.Test.main(Test.java:23)
Caused by: java.lang.RuntimeException: 不允许通过这个构造器来生成实例at com.wfg.design.mode5.HungaryMode2.<init>(HungaryMode2.java:11)at com.wfg.design.mode5.HungaryMode2.<clinit>(HungaryMode2.java:8)... 1 more

8.枚举单例模式

枚举单例模式是推荐的单例模式,它不仅可以防御序列化破坏,也可以防御反射破坏
1.定义一个枚举类,里面设置想要的实例对象

public enum EnumMode {ENUMMODE;private Object obj;public Object getObj() {return obj;}public void setObj(Object obj) {this.obj = obj;}public static EnumMode getInstance(){return ENUMMODE;}
}

2.测试是否是单例的

 public static  void main(String[]args)  {EnumMode enumMode=EnumMode.getInstance();enumMode.setObj(new Object());EnumMode enumMode2=EnumMode.getInstance();System.out.println(enumMode.getObj());System.out.println(enumMode2.getObj());System.out.println(enumMode==enumMode2);}//java.lang.Object@27716f4//java.lang.Object@27716f4//true

从输出上看到,是单例的,还是同一个对象
3.测试序列化破坏

//序列化破坏public static  void main(String[]args) throws IOException, ClassNotFoundException {EnumMode enumMode=EnumMode.getInstance();enumMode.setObj(new Object());//对象输出ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("file"));outputStream.writeObject(enumMode);//对象输入ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("file"));EnumMode enumMode2 = (EnumMode) inputStream.readObject();System.out.println(enumMode.getObj());System.out.println(enumMode2.getObj());System.out.println(enumMode==enumMode2);}//java.lang.Object@4a574795//java.lang.Object@4a574795//true

从输出上看到,创建的实例对象,也没有被序列化破坏,还是同一个

//反射破坏public static  void main(String[]args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {EnumMode enumMode=EnumMode.getInstance();enumMode.setObj(new Object());//通过反射创建了HungaryMode2实例Class<EnumMode> cs=EnumMode.class;// 枚举类只包含一个(String,int)类型构造器Constructor<EnumMode> ct=cs.getDeclaredConstructor(String.class,int.class);//打开构造权限ct.setAccessible(true);EnumMode enumMode2=ct.newInstance("aa",1);System.out.println(enumMode);System.out.println(enumMode2);System.out.println(enumMode==enumMode2);}

抛出异常

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objectsat java.lang.reflect.Constructor.newInstance(Constructor.java:417)at com.wfg.design.mode5.Test2.main(Test2.java:42)

由于Java禁止通过反射创建枚举对象,抛出了错误。正是因为枚举类型拥有这些先天的优势,所以用它创建单例也是不错的选择,
下一篇讲讲建造者模式

设计模式系列(创建型模式)之三单例模式相关推荐

  1. 设计模式(创建型模式)——单例模式(Singleton)

    2019独角兽企业重金招聘Python工程师标准>>> 单例对象(Singleton)是一种常用的设计模式.在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在.这 ...

  2. GOF23设计模式(创建型模式)单例模式

    目录: 一:单例模式的核心作用.常见应用场景 二:五种单例模式及其实现 三:关于反射和反序列化破解单例模式的漏洞,以及相应的解决方案 四:测试五种单例模式的效率 一:核心作用及常见应用场景: 核心作用 ...

  3. 从FLC中学习的设计模式系列-创建型模式(3)-工厂方法

    工厂方法是一组方法, 他们针对不同条件返回不同的类实例,这些类一般有共同的父类. 工厂方法模式 来自: http://zh.wikipedia.org/wiki/工厂方法模式 工厂方法模式 是一种面向 ...

  4. Java设计模式学习总结(4)——创建型模式之单例模式

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创建自己的对 ...

  5. Java学习--设计模式之创建型模式

    一.简介 创建型模式:这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象.这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活.创建型模式包括:工 ...

  6. 设计模式之创建型模式(工厂、原型、建造者)

    文章目录 创建型模式 2.1 工厂设计模式 2.1.1 简单工厂模式 2.1.2 工厂方法模式 2.1.3 抽象工厂 2.1.4 工厂模式总结 2.1.5 Spring中的工厂模式 2.1.6 工作中 ...

  7. 创建型模式:单例模式(懒汉+饿汉+双锁校验+内部类+枚举)

    单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创 ...

  8. 创建型模式、结构型模式和行为型模式_设计模式之创建型模式

    设计模式GOF23(Group of Four) 设计模式可分为三种类型: 创建型模式:单例模式,工厂模式,抽象工厂模式,建造者模式,原型模式. 结构型模式:适配器模式,桥接模式,装饰模式,组合模式, ...

  9. 备战面试日记(3.2) - (设计模式.23种设计模式之创建型模式)

    本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试. 记录日期:2022.1.6 大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习. 文章 ...

  10. java设计模式中不属于创建型模式_23种设计模式第二篇:java工厂模式定义:工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式...

    23种设计模式第二篇:java工厂模式 定义: 工厂模式是 Java 中最常用的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 工厂模式主要是为创建对象提供过渡接口, ...

最新文章

  1. linux下添加路由的方法
  2. java z+_Java Z 字形变换
  3. .NET MVC3使用CheckBox List(复选框列表)的简单方法
  4. 战斧湖战役之后的5rnm
  5. 基类成员的public访问权限在派生类中变为_第17篇:C++继承中虚表的内存布局
  6. Flutter自定义相机,Flutter相册选择照片
  7. 如何通过Facebook幻灯片广告让销售量疯涨
  8. Android Stutio 3.0 - Gradle sync failed
  9. 【Axure PR原型模板】微信公众小程序手机移动端高保真交互原型
  10. 9个免费的矢量图网站
  11. 2020家用千兆路由器哪款好_2020年500元以内23款无线路由器推荐,贵就好吗?
  12. c语言如何宏定义枚举型结构体,C语言学习笔记--枚举结构体
  13. kubeadm安装K8s 1.16集群--问题集锦
  14. 做好项目成本核算需要注意哪些事项
  15. USRP 型号对比与挑选
  16. 破解Redhat开机密码过程
  17. php求三个数中间值
  18. ant-design官网打不开时,你需要使用以下的镜像地址打开网站
  19. SEC主席:ICO要作为证券进行注册
  20. JAVA经验:很有启发(三)

热门文章

  1. 华南理工计算机世界什么水平,华南理工大学考研难吗?一般要什么水平才可以进入?...
  2. 人间不正经生活语录(四)
  3. 医院信息系统HIS源码——接口技术:RESTful API + WebSocket + WebService
  4. 飞腾CPU体系结构(十三)
  5. 信息论基础学习笔记(零)——通信系统模型及经典信息论
  6. 网络地址转换NAT原理(易于理解)
  7. 网络技术:NAT 网络地址转换及原理
  8. vue打包上线移除 console
  9. STM32F103之系统时钟初始化及延迟函数
  10. mysql timestamp 默认格式_MySQL-TIMESTAMP(3)的默认值