当前位置:   article > 正文

单例模式详解

单例模式

目录

一、什么是单例模式

二、单例模式的结构

三、单例模式分类

四、单例模式优缺点

五、创建单例模式

饿汉式

1. 静态成员变量方式

2.静态代码块方式

懒汉式

1.线程不安全

2.线程安全(优化)

3.双重检查锁模式

4. 静态内部类方式

5.枚举方式


一、什么是单例模式

        单例模式是Java中最简单的设计模式之一。这种类型的设计模式属于创建者模式,它提供了一种访问对象的最佳方式。

        这种设计模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。

二、单例模式的结构

单例类:只能创建一个实例的类

访问类:使用单例类的类

三、单例模式分类

饿汉式:类加载就会导致该单实例对象被创建

懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时被创建

四、单例模式优缺点

优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。

2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

注意:

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。

五、创建单例模式

饿汉式

存在问题:

类加载时对象就被创建,一直在内存中,如果一直不适用,该对象仍在,会存在内存浪费问题

1. 静态成员变量方式

  1. public class HungryChinese {
  2. //私有构造方法
  3. private HungryChinese(){}
  4. //在该类中创建一个该类的对象供外界去使用
  5. private static HungryChinese hungryChinese = new HungryChinese();
  6. //提供一个公共的访问方式,让外界获取hungryChinese对象
  7. public static HungryChinese getInstance(){
  8. return hungryChinese;
  9. }
  10. }
  11. class HungryChineseTest{
  12. public static void main(String[] args) {
  13. //获取单例类的对象,因为对象私有,只能通过方法去获取
  14. HungryChinese instance = HungryChinese.getInstance();
  15. HungryChinese instance1 = HungryChinese.getInstance();
  16. //判断是否为同一个对象
  17. System.out.println(instance.equals(instance1));
  18. }
  19. }

2.静态代码块方式

  1. public class HungryChinese2 {
  2. //私有构造方法,为了不让外界创建该类的对象
  3. private HungryChinese2(){}
  4. //声明该类类型的变量
  5. private static HungryChinese2 hungryChinese2;//初始值为null
  6. //静态代码块中赋值
  7. static {
  8. hungryChinese2 = new HungryChinese2();
  9. }
  10. //对外提供的访问方式
  11. public static HungryChinese2 getInstance(){
  12. return hungryChinese2;
  13. }
  14. }
  15. class HungryChinese2Test{
  16. public static void main(String[] args) {
  17. HungryChinese2 instance = HungryChinese2.getInstance();
  18. HungryChinese2 instance1 = HungryChinese2.getInstance();
  19. System.out.println(instance.equals(instance1));
  20. }
  21. }

懒汉式

1.线程不安全

  1. public class LazyMan {
  2. //私有构造方法,为了不让外界创建该类的对象
  3. private LazyMan(){}
  4. //声明LazyMan类型的变量
  5. private static LazyMan instance;//只是声明了该类的对象,没有赋初始值
  6. //对外提供访问方式
  7. public static LazyMan getInstance(){
  8. //判断instance是否为null,如果为null,说明还没有创建LazyMan类的对象
  9. //如果没有,创建一个并返回;如果有,直接返回
  10. //线程不安全,多线程下会创建多个对象
  11. if (instance == null){
  12. instance = new LazyMan();
  13. }
  14. return instance;
  15. }
  16. }
  17. class LazyManTest{
  18. public static void main(String[] args) {
  19. LazyMan instance = LazyMan.getInstance();
  20. LazyMan instance1 = LazyMan.getInstance();
  21. System.out.println(instance.equals(instance1));
  22. }
  23. }

2.线程安全(优化)

  1. public class LazyMan2 {
  2. //私有构造方法,为了不让外界创建该类的对象
  3. private LazyMan2(){}
  4. //声明LazyMan类型的变量
  5. private static LazyMan2 instance;//只是声明了该类的对象,没有赋初始值
  6. //对外提供访问方式
  7. public static LazyMan2 getInstance(){
  8. //判断instance是否为null,如果为null,说明还没有创建LazyMan类的对象
  9. //如果没有,创建一个并返回;如果有,直接返回
  10. if (instance == null){
  11. //线程1等待,线程2获取到cpu执行权,也会进入到该判断里
  12. instance = new LazyMan2();
  13. }
  14. return instance;
  15. }
  16. }
  17. class LazyMan2Test{
  18. public static synchronized void main(String[] args) {
  19. LazyMan2 instance = LazyMan2.getInstance();
  20. LazyMan2 instance1 = LazyMan2.getInstance();
  21. System.out.println(instance.equals(instance1));
  22. }
  23. }

3.双重检查锁模式

        双重检查锁模式解决了单例、性能、线程安全问题,看似完美无缺,其实存在问题,在多线程情况下,可能会出现空指针问题问题在于JVM在实例化对象时会进行优化和指令重排序操作。解决空指针问题只需使用volatile关键字,volatile可以保证可见性和有序性。

  1. public class LazyMan3 {
  2. private LazyMan3(){}
  3. private static volatile LazyMan3 instance;
  4. public static LazyMan3 getInstance(){
  5. //第一次判断,如果instance不为null,不需要抢占锁,直接返回对象
  6. if (instance == null){
  7. synchronized (LazyMan3.class){
  8. //第二次判断
  9. if (instance == null){
  10. instance = new LazyMan3();
  11. }
  12. }
  13. }
  14. return instance;
  15. }
  16. }
  17. class LazyMan3Test{
  18. public static void main(String[] args) {
  19. LazyMan3 instance = LazyMan3.getInstance();
  20. LazyMan3 instance1 = LazyMan3.getInstance();
  21. System.out.println(instance == instance1);
  22. }
  23. }

4. 静态内部类方式

        静态内部类模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的方法/属性被调用时才会被加载,并初始化静态属性,静态属性由于被static修饰,保证只能被初始化一次,并且严格保证实例化顺序。

        静态内部类模式是一种优秀的单例模式。在没有任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间浪费。

  1. public class LazyMan4 {
  2. private LazyMan4(){}
  3. //定义一个静态内部类
  4. private static class LazyMan4Holder{
  5. private static final LazyMan4 INSYANCE = new LazyMan4();
  6. }
  7. //对外访问方法
  8. public static LazyMan4 getInstance(){
  9. return LazyMan4Holder.INSYANCE;
  10. }
  11. }
  12. class LazyMan4Test{
  13. public static void main(String[] args) {
  14. LazyMan4 instance = LazyMan4.getInstance();
  15. LazyMan4 instance1 = LazyMan4.getInstance();
  16. System.out.println(instance == instance1);
  17. }
  18. }

5.枚举方式

枚举方式属于饿汉式方式

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举是线程安全的,并且只会装载一次,枚举类是所有单例类实现中唯一不会被破坏的单例模式。

  1. public enum LazyMan5 {
  2. INSTANCE;
  3. }
  4. class LazyMan5Test{
  5. public static void main(String[] args) {
  6. LazyMan5 instance = LazyMan5.INSTANCE;
  7. LazyMan5 instance1 = LazyMan5.INSTANCE;
  8. System.out.println(instance == instance1);
  9. }
  10. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/729612
推荐阅读
相关标签
  

闽ICP备14008679号