前言

大聪明在写代码的过程中发现设计模式的影子是无处不在,设计模式也是软件开发人员在软件开发过程中面临的一般问题的解决方案。大聪明本着“独乐乐不如众乐乐”的宗旨与大家分享一下设计模式的学习心得。
今天就与大家分享一下单例模式中最常用的两种模式——懒汉模式与饿汉模式。

1.单例设计模式

首先先简单的说一下什么叫单例设计模式。单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

需要注意的是

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

简单的描述了一下什么是设计模式,那下面就由大聪明来与大家一一分享懒汉模式和饿汉模式的学习收获与心得。

(1)懒汉模式

懒汉模式,顾名思义就是特别懒,在类加载时不初始化,等到第一次被使用时才初始化。下面是一个简单的实例:

public class Singleton {
	//创建实例,注意,此时没有new
    private static Singleton instance = null;
    //构造方法私有化,无法在外部获取实例,只能通过下方的公有静态方法
    private Singleton() {
    }
    //公有的静态方法,返回实例对象
    public static Singleton getInstance() {
    	//判断是否存在实例,没有才实例化
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

这是懒汉模式中最简单的一种写法,只有在方法第一次被访问时才会实例化,从而达到了懒加载的效果。但是这种写法有个很严重的问题,它无法保证多线程的安全性。举个简单的例子,假如对象还没被实例化,这时候有两个线程同时访问,那么就可能出现多次实例化的结果,所以这种写法不可采用。
那么大聪明就产生了一个疑问,有没有既简单又可用的懒汉模式写法呢?
那肯定是有的,只需要给上面的代码加一个锁就可以啦

public class Singleton {
	//创建实例,注意,此时没有new
    private static Singleton instance = null;
    //构造方法私有化,无法在外部获取实例,只能通过下方的公有静态方法
    private Singleton() {
    }
    //公有的静态方法,返回实例对象
    public static synchronized Singleton getInstance() {
    	//判断是否存在实例,没有才实例化
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

这种写法是对getInstance()加了一个锁的处理,保证了同一时刻只能有一个线程访问并获得实例,但是它也有一个很明显的缺点,因为synchronized是修饰整个方法,每个线程访问都要进行同步,所以这样就会使得效率变的很低,但其实这个方法只执行一次实例化就可以了。
为了改进这种写法,就有了下面的双重检查懒汉式。

public class Singleton {
	//创建实例,注意,此时没有new
    private static volatile Singleton instance;
    //构造方法私有化,无法在外部获取实例,只能通过下方的公有静态方法
    private Singleton() {}
    //公有的静态方法,返回实例对象
    public static Singleton getInstance() {
    	//判断是否存在实例,没有才实例化
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这种写法用了两个判断,更重要的是它同步的是代码块而非方法,这样效率就会大大提升。简单的解释一下为什么要用两个if判断:这是为了线程安全考虑,还是那个场景,对象还没实例化,两个线程A和B同时访问静态方法并同时运行到第一个if判断语句,这时线程A先进入同步代码块中实例化对象,结束之后线程B也进入同步代码块,如果没有第二个if判断语句,那么线程B也同样会执行实例化对象的操作了,但此时我们加上了第二个if判断,这样就不会重复执行对象的实例化操作,也就大大提升了代码效率。

(2)饿汉模式

饿汉模式,顾名思义它很饥饿,创建对象的时候就直接进行实例化操作。

public class Singleton {
	//创建实例的时候就new
	private static Singleton instance = new Singleton();
	// 私有化构造方法,外部不能new
	private Singleton() {}
	//公有的静态方法,返回实例对象
	public static Singleton getInstance() {
		//直接将事先new好的实例返回
		return instance;
	}
}

这是比较常见的写法,在类加载的时候就完成了实例化,避免了多线程的同步问题,适合调用较为频繁类,而且效率较高。但是缺点也是很明显的,在类加载时就进行实例化,没有达到Lazy Loading (懒加载) 的效果,如果这个实例没用被使用,那么内存就浪费了。

2.单例模式的使用场景

上面为大家简单讲解了一下单例模式,下面再说说它的应用场景。

  • Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
  • windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
  • 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  • 应用程序的日志应用,一般都使用单例模式实现,这一般是因为共享的日志文件一直处于打开状态,只能有一个实例去操作,否则内容不好追加。
  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常巨大的,使用单例模式来维护,就可以大大降低这种损耗。
  • 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

小结

单例模式是开发过程中使用的最多的一种设计模式,它只允许创建一个对象,因此节省内存,加快了对象访问速度,当对象需要被公用的场景就很适合使用单例模式;但是它不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。所以我们在开发的过程中要根据不同的应用场景选择更合适的设计模式,这样才能帮助我们更好的解决问题。

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

你在被打击时,记起你的珍贵,抵抗恶意;
你在迷茫时,坚信你的珍贵,抛开蜚语;
爱你所爱 行你所行 听从你心 无问东西