设计模式之单例

文章目录

  • 前言
  • 一、单例模式的基本定义
  • 二、懒汉式单例
  • 三、饿汉式单例
  • 四、懒汉式单例存在的线程安全问题
  • 总结


前言

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


一、单例模式的基本定义

单例模式(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);
    }
}

设计模式之单例_第1张图片
如图三次调用都指向同一个对象,因为从第一个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);
    }
}

设计模式之单例_第2张图片

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

懒汉式单例是在getInstance()被调用的时候创建的,若此时有大量线程访问此方法,就会创建多个对象,我们可以测试一下:
设计模式之单例_第3张图片
在创建对象的时候先让线程睡眠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个线程。运行结果如下图:
设计模式之单例_第4张图片
创建的对象很多不一样。解决线程安全问题,一般可以在可能出现并发安全的代码块加上对象锁,就是synchronized锁,我们上锁之后,再试试:
设计模式之单例_第5张图片
测试结果:
设计模式之单例_第6张图片
就可以保证创建的对象是同一个。


总结

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

你可能感兴趣的:(设计模式之美,设计模式,java,单例模式)