【设计模式-2.1】创建型——单例模式
说明:设计模式根据用途分为创建型、结构性和行为型。创建型模式主要用于描述如何创建对象,本文介绍创建型中的单例模式。
饿汉式单例
单例模式是比较常见的一种设计模式,旨在确保对象的唯一性,什么时候去使用这个对象都是同一个。这样设计的目的是为了避免对象重复创建,浪费资源,同时也保证了对象的唯一性,不至于多个相同的对象,状态不一致的情况。
以下是单例模式的简单实现:
/**
* 太阳类
*/
public class Sun {
private static final Sun sun = new Sun();
private Sun() {
}
public static Sun getInstance() {
return sun;
}
}
需要注意,对象的创建是在对象类内部,且为static final修饰,并且私有化构造方法,使其不能在外部被创建,其次创建一个静态方法,返回其对象;
此时,外部通过类名.方法名的方式,可以访问到该对象,且始终为同一个;
public class Test {
public static void main(String[] args) {
Sun sun1 = Sun.getInstance();
Sun sun2 = Sun.getInstance();
System.out.println(sun1 == sun2);
}
}
两次获取到的对象相同
上面这种创建方式,称为饿汉式单例(Eager Singleton),即类加载完成时就创建对象;
懒汉式单例
以下这种方式,称为懒汉式单例(Lazy Singleton),在调用静态方法时才创建对象;
/**
* 懒汉式单例模式
*/
public class LazySun {
private static LazySun sun = null;
private LazySun() {
}
public static LazySun getInstance() {
if (sun == null) {
sun = new LazySun();
}
return sun;
}
}
这种创建方式,可以实现延迟加载,节约资源。但是在多线程并发时,如果有多个线程在if (sun == null),就会导致对象被创建多次,所以我们很容易想到的是加锁,将静态方法加上锁,如下:
/**
* 懒汉式单例模式
*/
public class LazySun {
private static LazySun sun = null;
private LazySun() {
}
public synchronized static LazySun getInstance() {
if (sun == null) {
sun = new LazySun();
}
return sun;
}
}
但是,给方法加上锁后,会影响性能,可以考虑减少锁的范围,只锁住创建对象这一行,如下:
/**
* 懒汉式单例模式
*/
public class LazySun {
private static LazySun sun = null;
private LazySun() {
}
public static LazySun getInstance() {
if (sun == null) {
synchronized (LazySun.class) {
sun = new LazySun();
}
}
return sun;
}
}
双重检查锁定
但是,还有问题。在多线程并发下,如果线程A在创建对象(未完成),线程B、C在if (sun == null) 这里判断为true,即便对象创建完成,线程B、C通过了if判断,也还是会依次再创建对象,也造成了对象被重复创建。因此,还需要改造,如下:
/**
* 双重检查锁定
*/
public class LazySun {
private volatile static LazySun sun = null;
private LazySun() {
}
public static LazySun getInstance() {
// 第一次判断
if (sun == null) {
synchronized (LazySun.class) {
// 第二次判断
if (sun == null){
sun = new LazySun();
}
}
}
return sun;
}
}
就是在锁住的代码块里面再进行一次非空判断,称为双重检查锁定(Double-Check Locking),同时,单例对象修饰符加上volatile,确保多个线程都能正确处理;
IoDH
饿汉式单例,是在类被加载时就创建了对象。不需要考虑多线程,但是无论是否使用到对象都创建,造成了资源浪费。而懒汉式单例,虽然做到了延迟加载,但是需要处理好多线程情况下的对象创建,使用了锁,影响了性能。
那有没有一种更好了方式,既能在多线程下使用,又不会影响性能?
在《设计模式的艺术》(刘伟著)中作者提供了一种更好的创建方式,称为IoDH(Initialization on Demand Holder,按需初始化),代码如下:
/**
* IoDH
*/
public class Singleton {
private Singleton() {
}
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
}
这种方式是对饿汉式单例的改进,是在单例类里创建了一个内部类,将单例对象的创建放在了这个内部类里面。因为单例对象不是单例类的一个成员变量,所以对象在类加载时不会被创建,而是会在调用静态方法时被创建,这样既能延迟加载,又没有使用锁,影响性能,一举两得。
书中说,通过使用IoDH,既可以实现延迟加载,又可以保证线程安全,不影响系统性能。因此,IoDH不失为一种最好的Java语言单例模式实现方式;其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH。
总结
本文参考《设计模式的艺术》、《秒懂设计模式》两书