设计模式-创建型模式-单例模式-懒汉式

设计模式-创建型模式-单例模式-懒汉式

一、创建型模式

创造型模式主要的关注点是怎么创建对象,主要特点是将对象的创建和使用分离开来。以此降低系统的耦合度,使用者可以不需要过分关注对象的创建细节,直接使用就可以。

创造模式例子:

  1. 去商城购买商品,并不需要知道商品是怎么来的怎么生产的,只需要买来使用就可以;
  2. 去饭店吃饭,不需要考虑才是怎么做出来的,直接点了吃就完事;

二、单例模式

单例模式是创建型模式的其中一个模式,一般只一个类只有一个实例(只实例化一次),并且这个这个实例化的过程是在这个类本身完成的,只是对外开放了一个获取这个实例了的对象的方法。

单例模式例子:

  1. win系统内的回收站就是全局唯一的,不论在哪里删除或者创建图标,整个系统内只有一个回收站实例,创建的多个图标也都是指向了这个实例;

三、懒汉式

懒汉式单例是指类在用的时候才会实例化,并且只有在第一次被调用时会实例化一次,多次调用都是返回第一次实例化的对象。

1.单线程懒汉式单例

一下是简单地一个样例,该例子可以在单线程模式下保持全局单例:

代码段1:单线程懒汉模式单例

//代码段1:单线程懒汉模式单例
package com.lqh.singleton;

/**
 * 单例模式-懒汉式
 * @author lqh
 * @version 1.0
 * @date: 2021/7/9 21:23
 */
public class LazySingleton {
    /**
     * 在类内私有化一个静态对象,也就是唯一的实例,外面获取的实例都是指向这个对象
     */
    private static LazySingleton instance;

    /**
     * 构造方法私有化,防止在其他地方被实例化
     * 此处虽然私有化了构造方法,但是还是能被反射获取到并调用
     */
    private LazySingleton(){}

    /**
     * 返回本类的实例对象,如果对象为空代表还没有被实例化,
     * 就需要调用构造方法实例化后返回
     * @return
     */
    public static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }

}

然后运行测试一下

代码段2:单线程测试懒汉模式单例

//代码段2:单线程测试懒汉模式单例
package com.lqh;

import com.lqh.singleton.LazySingleton;

import java.util.ArrayList;
import java.util.List;

/**
 * 主类
 * @date 22:24 2021/7/9
 * @author lqh
 */
public class Main {

    /**
     * 比较两个对象是否相同
     * @param i1
     * @param i2
     */
    private static void equals(LazySingleton i1, LazySingleton i2){
        if (!i1.equals(i2)){
            System.out.println("有不同的实例!");
        }
    }

    public static void main(String[] args) {
        //获取对象
        LazySingleton instance1 = LazySingleton.getInstance();
        //打印哈希码值
        System.out.println(instance1.toString());//com.lqh.singleton.LazySingleton@1b6d3586

        LazySingleton instance2 = LazySingleton.getInstance();
        //打印哈希码值
        System.out.println(instance2.toString());//com.lqh.singleton.LazySingleton@1b6d3586

        //比较两个对象是否相同
        System.out.println(instance1.equals(instance2));//true

        //获取十次并加入列表
        List<LazySingleton> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            //获取实例
            LazySingleton instance = LazySingleton.getInstance();
            //将获取的实例加入列表
            list.add(instance);
        }

        //遍历列表打印每个实例的哈希码值
        list.forEach(x -> System.out.println(x));//无一例额外全部都是com.lqh.singleton.LazySingleton@1b6d3586

        //遍历列表将每个实例都与列表内的所有实例比对一遍,有不同的就输出不同
        list.forEach(x -> {
            for (LazySingleton instance : list) {
                equals(x, instance);//全部相同
            }
        });

    }
}

运行结果:

com.lqh.singleton.LazySingleton@1b6d3586
com.lqh.singleton.LazySingleton@1b6d3586
true
com.lqh.singleton.LazySingleton@1b6d3586
com.lqh.singleton.LazySingleton@1b6d3586
com.lqh.singleton.LazySingleton@1b6d3586
...

上面可以看到在main线程下生成的所有LazySingleton对象都指向了同一个哈希码值的对象,毕竟他们都是调用的同一个方法(getInstance):

//代码段1片段
public static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }

并且返回的也都是instance这个对象:

//代码段1片段
private static LazySingleton instance;

但是如果在多线程模式下这个方法就不在适用,因为多线程模式下可以看做是两个或多个线程同时在执行,比如两个线程A和B,线程A在调用getInstance()方法的时候检测到instance对象为空实例化了一个对象,线程B在A正在实例化对象的时候也调用了getInstance()方法,此时A线程还没有实例化完成,导致B线程也检测到instance对象时空,也会进行实例化,这就导致了两个对象的生成,打破了单例模式。

多线程测试:

代码段3:多线程测试懒汉模式单例

//代码段3:多线程测试懒汉模式单例
package com.lqh;

import com.lqh.singleton.LazySingleton;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 主类
 * @date 22:24 2021/7/9
 * @author lqh
 */
public class Main {

    /**
     * 比较两个对象是否相同
     * @param i1
     * @param i2
     */
    private static void equals(LazySingleton i1, LazySingleton i2){
        if (!i1.equals(i2)){
            System.out.println("有不同的实例!");
        }
    }

    public static void main(String[] args) {
        //创建一个线程安全的列表,用来保存实例对象
        List<LazySingleton> list = Collections.synchronizedList(new ArrayList<>());

        //创建十个线程,且每个线程获取LazySingleton实例
        for (int i = 0; i < 10; i++) {
            new Thread(() ->{
                LazySingleton instance = LazySingleton.getInstance();
                list.add(instance);
            }, "线程" + (i+1)).start();
        }

        //遍历列表打印每个实例的哈希码值
        list.forEach(x -> System.out.println(x));

        //遍历列表比较每一个实例是否相同
        list.forEach(x -> {
            for (LazySingleton instance : list) {
                equals(x,instance);
            }
        });

    }
}

运行结果:

com.lqh.singleton.LazySingleton@3d075dc0
com.lqh.singleton.LazySingleton@214c265e
com.lqh.singleton.LazySingleton@448139f0
com.lqh.singleton.LazySingleton@7cca494b
com.lqh.singleton.LazySingleton@7cca494b
com.lqh.singleton.LazySingleton@448139f0
com.lqh.singleton.LazySingleton@448139f0
com.lqh.singleton.LazySingleton@448139f0
com.lqh.singleton.LazySingleton@448139f0
com.lqh.singleton.LazySingleton@448139f0
有不同的实例!
有不同的实例!
有不同的实例!
有不同的实例!
有不同的实例!
...

可以看到在多线程模式下就产生了多个不同的对象,为了避免这个情况就需要用到锁了,改进一下代码,只改动了getInstance()方法:

2.多线程懒汉式单例

代码段4:懒汉模式单例加锁(多线程)

//代码段4:懒汉模式单例加锁
package com.lqh.singleton;

/**
 * 单例模式-懒汉式
 * @author lqh
 * @version 1.0
 * @date: 2021/7/9 21:23
 */
public class LazySingleton {
    /**
     * 在类内私有化一个静态对象,也就是唯一的实例,外面获取的实例都是指向这个对象
     */
    private static LazySingleton instance;

    /**
     * 构造方法私有化,防止在其他地方被实例化
     * 此处虽然私有化了构造方法,但是还是能被反射获取到并调用
     */
    private LazySingleton(){}

    /**
     * 返回本类的实例对象,如果对象为空代表还没有被实例化,
     * 就需要调用构造方法实例化后返回
     * @return
     */
    public static LazySingleton getInstance(){
        //在检测时加一层锁,锁住本类
        synchronized(LazySingleton.class){
            if (instance == null){
                instance = new LazySingleton();
            }
            return instance;
        }
    }

}

再次运行单线程(代码段2)和多线程(代码段3)的代码:

单线程:

com.lqh.singleton.LazySingleton@1b6d3586
com.lqh.singleton.LazySingleton@1b6d3586
true
com.lqh.singleton.LazySingleton@1b6d3586
com.lqh.singleton.LazySingleton@1b6d3586
...

多线程:

com.lqh.singleton.LazySingleton@3d075dc0
com.lqh.singleton.LazySingleton@3d075dc0
com.lqh.singleton.LazySingleton@3d075dc0
...

可以看到在加了一次锁之后在多线程模式下就又成功回到单例模式

3.完整版懒汉模式单例

由于以下内容暂时还无法理解所以先只是贴出有待再研究

package com.lqh.singleton;

/**
 * 单例模式-懒汉式
 * @author lqh
 * @version 1.0
 * @date: 2021/7/9 21:23
 */
public class LazySingleton {

    /**
     * 在类内私有化一个静态对象,也就是唯一的实例,外面获取的实例都是指向这个对象
     * 新增volatile关键字防止指令重排(?何为指令重排)
     */
    private static volatile LazySingleton instance;

    /**
     * 构造方法私有化,防止在其他地方被实例化
     * 此处虽然私有化了构造方法,但是还是能被反射获取到并调用
     */
    private LazySingleton(){}

    /**
     * 返回本类的实例对象,如果对象为空代表还没有被实例化,
     * 就需要调用构造方法实例化后返回
     * @return
     */
    public static LazySingleton getInstance() {
        //第一层检查是否实例化对象
        if (instance == null) {
            //加锁避免线程不安全
            synchronized (LazySingleton.class) {
                //再次检查是否有实例化,没有就实例化(?为何二次检查)
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        //返回
        return instance;
    }

}

运行单线程(代码片段2)和多线程(代码片段3)的测试结果和多线程懒汉式单例的结果一样就不再次贴出

上一篇:2021-07-12 内部类和单例模式


下一篇:设计模式3 - 单例模式【Singleton Pattern】