设计模式之单例


前言

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


一、单例模式的基本定义

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

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

二、懒汉式单例

代码实现如下:

public class LazyWaterCup {
    // 定义一个静态引用
    private static LazyWaterCup wc;

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

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

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

测试结果如下:

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()方法就不会再创建对象而是将上一个对象地址返回。因此三次打印结果都是指向同一个对象。

三、饿汉式单例

代码如下:

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

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

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

测试结果如下:

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,方便模拟多线程环境。

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锁,我们上锁之后,再试试:
在这里插入图片描述
测试结果:
在这里插入图片描述
就可以保证创建的对象是同一个。


总结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员阿坤...

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值