设计模式之单例

文章目录


前言

单例模式是比较经典的设计模式,本篇我们将结合实际案例说明单例模式的实现原理,以及存在的问题。


一、单例模式的基本定义

单例模式(Singleton Pattern)确保某一个类只有一个实例方法,而且自行实例化并向整个系统提供这个实例,这个类被称为单例类,它提供全局访问方法。单例模式的要点有三个:

  • 某个类只能有一个实例
  • 它必须自行创建这个实例
  • 它必须自行向整个系统提供这个实例

二、懒汉式单例

代码实现如下:

java 复制代码
public class LazyWaterCup {
    // 定义一个静态引用
    private static LazyWaterCup wc;

    // 构造方法私有
    private LazyWaterCup(){}

    // 提供一个全局访问点
    public static LazyWaterCup getInstance() {
        if (wc == null){
        	wc = new LazyWaterCup();
        }
        return wc;
    }
}

首先我们要声明一个类引用,其次构造方法必须是私有的,以保证外部对于此类无法直接通过new进行类的实例化,与此同时我们要提供一个对外的方法,在此方法中实现类的实例化,也需要进行一个判断,若类引用没有指向任何对象,就创建一个对象,让引用指向它。

测试结果如下:

java 复制代码
public class Test {
    public static void main(String[] args) {
        LazyWaterCup lwc1 = LazyWaterCup.getInstance();
        LazyWaterCup lwc2 = LazyWaterCup.getInstance();
        LazyWaterCup lwc3 = LazyWaterCup.getInstance();
        System.out.println("lwc1= " + lwc1);
        System.out.println("lwc2= " + lwc2);
        System.out.println("lwc3= " + lwc3);
    }
}

如图三次调用都指向同一个对象,因为从第一个getInstance()被调用时,由于类引用指向->null所以会创建对象,后续再调用getInstance()方法就不会再创建对象而是将上一个对象地址返回。因此三次打印结果都是指向同一个对象。

三、饿汉式单例

代码如下:

java 复制代码
public class HungryWaterCup {
    // 定义一个静态引用
    private final static HungryWaterCup wc = new HungryWaterCup();
    
    // 构造方法私有
    private HungryWaterCup(){}

    // 提供一个全局访问点
    public static HungryWaterCup getInstance() {
        return wc;
    }
}

饿汉式单例与懒汉式单例的区别在于它对于对象的创建比较急迫,在声明的时候就创建一个对象,使声明的引用指向它,它是在类的装载过程中就完成的。

测试结果如下:

java 复制代码
public class Test {
    public static void main(String[] args) {
        HungryWaterCup hwc1 = HungryWaterCup.getInstance();
        HungryWaterCup hwc2 = HungryWaterCup.getInstance();
        HungryWaterCup hwc3 = HungryWaterCup.getInstance();
        System.out.println("hwc1= " + hwc1);
        System.out.println("hwc2= " + hwc2);
        System.out.println("hwc3= " + hwc3);
    }
}

四、懒汉式单例存在的线程安全问题

懒汉式单例是在getInstance()被调用的时候创建的,若此时有大量线程访问此方法,就会创建多个对象,我们可以测试一下:

在创建对象的时候先让线程睡眠1ms,方便模拟多线程环境。

java 复制代码
public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 200; i++) {
            Thread t1 = new Thread(() -> {
                try {
                    LazyWaterCup lwc = LazyWaterCup.getInstance();
                    System.out.println("lwc->" + lwc);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }, "t1");
            t1.start();
        }
    }
}

在测试类代码中,创建200个线程。运行结果如下图:

创建的对象很多不一样。解决线程安全问题,一般可以在可能出现并发安全的代码块加上对象锁,就是synchronized锁,我们上锁之后,再试试:

测试结果:

就可以保证创建的对象是同一个。


总结

今天对单例模式的分析就到这里了,后续会继续总结其他设计模式的原理与实现。

相关推荐
七七软件开发3 分钟前
团购商城 app 系统架构分析
java·python·小程序·eclipse·系统架构·php
七七软件开发9 分钟前
打车小程序 app 系统架构分析
java·python·小程序·系统架构·交友
范纹杉想快点毕业14 分钟前
基于 C 语言视角:流程图中分支与循环结构的深度解析
c语言·stm32·单片机·设计模式·架构·流程图·uml
_祝你今天愉快15 分钟前
Java-JVM探析
android·java·jvm
学编程的司马光25 分钟前
Idea集成Jenkins Control插件,在IDEA中触发Jenkins中项目的构建
java·jenkins·intellij-idea
孟君的编程札记31 分钟前
别只知道 Redis,真正用好缓存你得懂这些
java·后端
幻雨様35 分钟前
UE5多人MOBA+GAS 番外篇:同时造成多种类型伤害,以各种属性值的百分比来应用伤害(版本二)
java·前端·ue5
爱吃小土豆豆豆豆1 小时前
登录校验一
java·大数据·数据库
热河暖男1 小时前
Spring Boot AI 极速入门:解锁智能应用开发
java·人工智能·spring boot·ai编程
lifallen1 小时前
hadoop.yarn 带时间的LRU 延迟删除
java·大数据·数据结构·hadoop·分布式·算法