设计模式学习笔记:单例模式

最近准备系统的学习一下设计模式,以前也只是泛泛的看过一些,没有做一些梳理或者总结,现在边学习边记录,也欢迎大家和我一起讨论,新手有很多不明白的地方。

1.基本概念

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

2.UML图

3.单例模式的实现

【方案一】最简单的单例模式(饿汉式)

// 饿汉式单例模式
public class Singleton {

    // 私有的构造器,防止外部实例化该对象
    private Singleton() {}
    
    // 内部声明该对象
    private static Singleton instance = new Singleton();
    
    // 公开的访问方法调用该对象的唯一实例
    public static Singleton getInstance() {
        return instance;
    }
}

方案一是一个简单的单例模式的实现,但是方案一存在的问题也很明显,就是在类加载的过程中,Singleton的对象instance已经自动被实例化,在程序开发中,通常我们使用单例模式的对象都是一个开销非常大的对象,可能在程序运行到很久之后才会使用这样的一个对象,因此,我们提前实例化可能会对资源产生很大的浪费,因此,在需要的时候实例化该对象,就是我们接下来要做的事情。

【方案二】延迟单例对象的实例化(懒汉式)

// 懒汉式单例模式
public class Singleton {

    // 私有的构造器,防止外部实例化该对象
    private Singleton() {}
    
    // 内部声明该对象
    private static Singleton instance = null;
    
    // 延迟声明对象
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

方案二解决了方案一中对象声明过早导致系统资源开销较大的问题,在第一次调用getInstance方法的时候才实例化该对象。但方案二却有一个严重的问题,当多线程执行该段代码时,可能会产生两个不同的Singleton对象,破坏了单例性。

我们来看下面这一段测试代码。

public class TestSingleton {
    public static void main(String[] args) {
        for (int i = 0; i < 4; i++) {
            Thread t = new Thread(new MyThread());
            t.start();
        }
    }
}

class MyThread implements Runnable {
    public void run() {
        Singleton mySingleton = Singleton.getInstance();
        System.out.println(mySingleton + ":" + mySingleton.hashCode());
    }
}

测试结果如下图所示:

 

那我们如何改进这样的一个单例模式呢?Java中的线程同步可能是一个很好的注意

【案例三】双重检查加锁

// 双重检查单例模式
public class Singleton {

    // 私有的构造器,防止外部实例化该对象
    private Singleton() {}
    
    // 内部声明该对象
    private volatile static Singleton instance = null;
    
    // 双重检查标准声明对象
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

利用双重检查加锁,首先检查了实例是否已经被创建了,如果为创建,才进行同步,这样一来,只有第一次才会同步,这正是我们想要的,即是线程安全的,又减少了getInstance方法时间的消耗。

但是该方法利用了JDK1.5的新功能,volatile修饰了instance实例,保证了instance对象的唯一性,如果不添加这个修饰符或者JDK版本过低,则双重检查加锁法也并不安全。

那是不是这样写法的单例模式就毫无漏洞呢?当然不是,反射还没有登场

【案例四】利用反射机制破坏单例模式

import java.lang.reflect.Constructor;

public class TestReflect {
    public static void main(String[] args) {
        // 先通过正常的方法取得一个singleton的实例
        Singleton singleton = Singleton.getInstance();
        
        Constructor<?> cons[] = Singleton.class.getDeclaredConstructors();
        cons[0].setAccessible(true);
        Singleton newSingleton = null;
        try {
            newSingleton = (Singleton) cons[0].newInstance();
        } catch (Exception e) {
        }
        System.out.println(singleton.hashCode());
        System.out.println(newSingleton.hashCode());
    }
}

结果如下:

一个防止利用反射机制创立单例对象的方法就是异常,在私有的构造方法中,如果instance对象的实例已经存在,并且仍然调用了构造方法,则抛出一个异常。但是通常我们不用考虑这些因素,因为在公司中,如果一个开发使用这样的方式去创建一个对象。。。那他的老大肯定把他劈头盖脸。。。

所以,我们在使用单例模式的时候,使用第三种的方法就已经存够了能够完成我们的要求

4.为什么要使用单例模式

很多人对设计模式可能存有误解,认为设计模式中的一些设计的方法使得我们的程序变得更加的复杂,实际上,他们却忽略了团队合作的重要性。例如单例模式,我们如果只是自己一个人编写程序,我们大可以保证自己只创建一个对象就行,不需要使用单例模式。但是在团队开发中,你并不能保证你的合作者和你能够协商在何时使用该对象,而且,在多线程的情况下,对象是否销毁,我们也不可知。如果单靠人为的去思考这些问题,岂不是太过复杂了。单例模式在这种情况下就帮助了我们太多太多,我们只用专心编程,而对于如果保持该对象只有一个问题,交给JVM就好了

原文地址:https://www.cnblogs.com/yijianguxin/p/2952583.html