单例模式详解
目录
一、什么是单例模式
单例模式是Java中最简单的设计模式之一。这种类型的设计模式属于创建者模式,它提供了一种访问对象的最佳方式。
这种设计模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
二、单例模式的结构
单例类:只能创建一个实例的类
访问类:使用单例类的类
三、单例模式分类
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时被创建
四、单例模式优缺点
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
注意:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
五、创建单例模式
饿汉式
存在问题:
类加载时对象就被创建,一直在内存中,如果一直不适用,该对象仍在,会存在内存浪费问题
1. 静态成员变量方式
public class HungryChinese { //私有构造方法 private HungryChinese(){} //在该类中创建一个该类的对象供外界去使用 private static HungryChinese hungryChinese = new HungryChinese(); //提供一个公共的访问方式,让外界获取hungryChinese对象 public static HungryChinese getInstance(){ return hungryChinese; }}class HungryChineseTest{ public static void main(String[] args) { //获取单例类的对象,因为对象私有,只能通过方法去获取 HungryChinese instance = HungryChinese.getInstance(); HungryChinese instance1 = HungryChinese.getInstance(); //判断是否为同一个对象 System.out.println(instance.equals(instance1)); }}
2.静态代码块方式
public class HungryChinese2 { //私有构造方法,为了不让外界创建该类的对象 private HungryChinese2(){} //声明该类类型的变量 private static HungryChinese2 hungryChinese2;//初始值为null //静态代码块中赋值 static { hungryChinese2 = new HungryChinese2(); } //对外提供的访问方式 public static HungryChinese2 getInstance(){ return hungryChinese2; }}class HungryChinese2Test{ public static void main(String[] args) { HungryChinese2 instance = HungryChinese2.getInstance(); HungryChinese2 instance1 = HungryChinese2.getInstance(); System.out.println(instance.equals(instance1)); }}
懒汉式
1.线程不安全
public class LazyMan { //私有构造方法,为了不让外界创建该类的对象 private LazyMan(){} //声明LazyMan类型的变量 private static LazyMan instance;//只是声明了该类的对象,没有赋初始值 //对外提供访问方式 public static LazyMan getInstance(){ //判断instance是否为null,如果为null,说明还没有创建LazyMan类的对象 //如果没有,创建一个并返回;如果有,直接返回 //线程不安全,多线程下会创建多个对象 if (instance == null){ instance = new LazyMan(); } return instance; }}class LazyManTest{ public static void main(String[] args) { LazyMan instance = LazyMan.getInstance(); LazyMan instance1 = LazyMan.getInstance(); System.out.println(instance.equals(instance1)); }}
2.线程安全(优化)
public class LazyMan2 { //私有构造方法,为了不让外界创建该类的对象 private LazyMan2(){} //声明LazyMan类型的变量 private static LazyMan2 instance;//只是声明了该类的对象,没有赋初始值 //对外提供访问方式 public static LazyMan2 getInstance(){ //判断instance是否为null,如果为null,说明还没有创建LazyMan类的对象 //如果没有,创建一个并返回;如果有,直接返回 if (instance == null){ //线程1等待,线程2获取到cpu执行权,也会进入到该判断里 instance = new LazyMan2(); } return instance; }}class LazyMan2Test{ public static synchronized void main(String[] args) { LazyMan2 instance = LazyMan2.getInstance(); LazyMan2 instance1 = LazyMan2.getInstance(); System.out.println(instance.equals(instance1)); }}
3.双重检查锁模式
双重检查锁模式解决了单例、性能、线程安全问题,看似完美无缺,其实存在问题,在多线程情况下,可能会出现空指针问题问题在于JVM在实例化对象时会进行优化和指令重排序操作。解决空指针问题只需使用volatile关键字,volatile可以保证可见性和有序性。
public class LazyMan3 { private LazyMan3(){} private static volatile LazyMan3 instance; public static LazyMan3 getInstance(){ //第一次判断,如果instance不为null,不需要抢占锁,直接返回对象 if (instance == null){ synchronized (LazyMan3.class){ //第二次判断 if (instance == null){ instance = new LazyMan3(); } } } return instance; }}class LazyMan3Test{ public static void main(String[] args) { LazyMan3 instance = LazyMan3.getInstance(); LazyMan3 instance1 = LazyMan3.getInstance(); System.out.println(instance == instance1); }}
4. 静态内部类方式
静态内部类模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的方法/属性被调用时才会被加载,并初始化静态属性,静态属性由于被static修饰,保证只能被初始化一次,并且严格保证实例化顺序。
静态内部类模式是一种优秀的单例模式。在没有任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间浪费。
public class LazyMan4 { private LazyMan4(){} //定义一个静态内部类 private static class LazyMan4Holder{ private static final LazyMan4 INSYANCE = new LazyMan4(); } //对外访问方法 public static LazyMan4 getInstance(){ return LazyMan4Holder.INSYANCE; }}class LazyMan4Test{ public static void main(String[] args) { LazyMan4 instance = LazyMan4.getInstance(); LazyMan4 instance1 = LazyMan4.getInstance(); System.out.println(instance == instance1); }}
5.枚举方式
枚举方式属于饿汉式方式
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举是线程安全的,并且只会装载一次,枚举类是所有单例类实现中唯一不会被破坏的单例模式。
public enum LazyMan5 { INSTANCE;}class LazyMan5Test{ public static void main(String[] args) { LazyMan5 instance = LazyMan5.INSTANCE; LazyMan5 instance1 = LazyMan5.INSTANCE; System.out.println(instance == instance1); }}
来源地址:https://blog.csdn.net/weixin_57504474/article/details/124494554
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341