Java设计模式之——单例模式

题引:

总结自己学习Java常用设计模式后的理解,本篇为单例模式。

未知的事物往往令人不知所措,为了透彻理解单例模式,我们必须知道:

什么是Java中的单例模式?

百度百科给出了Java中单例模式概念的内涵,Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”

由定义我们可以很清晰的抽象出:

实现Java单例模式类有哪些通用设计规则?

(1)私有化类构造器。
(2)定义静态私有的类对象。
(3)提供公共静态的获取该私有类对象的方法。

了解了单例模式的概念,以及单例模式的通用设计规则,对于如何实现一个Java单例,相信我们已不再感觉束手无策。但在了解具体实现之前,我们还需要了解:

Java单例模式解决了什么问题?

Java的单例模式主要解决了多线程并发访问共享资源的线程安全问题。

Java单例模式主要应用场景有哪些?

1.共享资源的访问与操作场景,如Windows系统的资源管理器,Windows系统的回收站,显卡的驱动程序,系统的配置文件,工厂本身(类模板),应用程序的日志对象等。

2.控制资源访问与操作的场景,如数据库的连接池,Java的线程池等。

单例模式的命名

单例的命名通常包含 singleton(以 singleton 开头或结尾) 或能按名称实际意义区分出在应用中唯一。

了解了Java单例模式出现的缘由以及出现的场合,那么,Java的单例究竟是以怎样的方式出现在这些场合中呢?

Java中单例模式的常用实现方式有哪些?

本文主要介绍4种常用的Java单例实现方式:饿汉式、懒汉式、注册登记式、序列化与反序列化式。

饿汉式

饿汉式,顾名思义,就是指在JVM首次访问到该单例类时,就会把该单例类对象创建出来,并保存在内存中,不管后续是否会使用到这个单例。

/**
 * @author jiangsj
 * 饿汉式
 * 优点:
 *     (1) 没有加任何的锁,执行效率较高
 *     (2) 线程安全
 * 缺点:
 *    (1) 类加载的时候就进行了初始化,程序后续未必会使用到该实例,导致内存浪费
 *    (2) 反射可破坏单例
 */
public class HungrySingleton {

    /** 私有化类构造器 */
    private HungrySingleton() {}

    /** 定义静态私有类对象 */
    private static HungrySingleton instance = new HungrySingleton();

    /** 提供公共静态的获取该私有类对象的方法 */
    public static HungrySingleton getInstance() {
        return instance;
    }

}

饿汉式-测试类

懒汉式

懒汉式,相比于饿汉式,它是指JVM首次访问到该单例类时,并不会实例化该单例类对象,只有等到后续被外部调用时,才会实例化该单例类对象。

饿汉式的写法比较固定,懒汉式由于延时加载的特性,写法上有一些变化,一般来说,懒汉式存在4个变种。

懒汉式1——无锁懒汉式
/**
 * @author jiangsj
 * 懒汉式——无锁,线程不安全
 * 优点:
 *     由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
 *
 * 缺点:
 *     (1) 该种实现方式存在线程不安全问题
 *     (2) 反序列化,反射与克隆可破坏单例
 */
public class LazySingletonWithoutSync {

    /** 私有化类构造器 */
    private LazySingletonWithoutSync() {}

    /** 定义静态私有类对象 */
    private static LazySingletonWithoutSync instance;

    /** 提供公共静态的获取该私有类对象的方法 */
    public static LazySingletonWithoutSync getInstance() {
        if (instance == null) {
            instance = new LazySingletonWithoutSync();
        }

        return instance;
    }

}

无锁懒汉式-测试类

懒汉式2——Sync同步锁懒汉式
/**
 * @author jiangsj
 * 懒汉式
 * 优点:
 *     (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
 *     (2) 线程安全
 * 缺点:
 *     (1) 给获取实例的公共方法加上同步锁synchronized,性能受到影响
 *     (2) 反序列化,反射与克隆可破坏单例
 */
public class LazySingletonWithSync {

    /** 私有化类构造器 */
    private LazySingletonWithSync() {}

    /** 定义静态私有类对象 */
    private static LazySingletonWithSync instance;

    /** 提供公共静态的获取该私有类对象的方法 */
    public static synchronized LazySingletonWithSync getInstance() {
        if (instance == null) {
            instance = new LazySingletonWithSync();
        }

        return instance;
    }

}

Sync同步锁懒汉式-测试类

懒汉式3——双重锁检查
/**
 * @author jiangsj
 * 懒汉式——双重锁检查单例
 * 优点:
 *     (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
 *     (2) 线程安全
 * 缺点:
 *     (1) 如果不加volatile关键词防止指令重排,双重锁检查单例可能会出现不完整实例
 *         分析:instance = new LazySingletonWithDoubleCheck() 操作并非原子操作,它包含如下三个操作指令:
 *             1) 分配对象的内存空间 memory = allocate()
 *             2) 初始化对象 ctorInstance(memory)
 *             3) 设置instance指向刚分配的内存地址 instance = memory
 *         经过指令重排序后,执行顺序可能如下:
 *             1) 分配对象的内存空间 memory = allocate()
 *             2) 设置instance指向刚分配的内存地址 instance = memory
 *             3) 初始化对象 ctorInstance(memory)
 *         若有A线程执行完上述重排序后的第二步,尚未初始化对象,此时B线程来获取单例instance,会发现instance不为空,于是返回该值,但实际该instance尚未构建完成,为不完整实例。
 *     (2) 反序列化,反射与克隆可破坏单例
 */
public class LazySingletonWithDoubleCheck {

    /** 私有化类构造器 */
    private LazySingletonWithDoubleCheck() {}

    /** 定义静态私有类对象 */
    private static volatile LazySingletonWithDoubleCheck instance;

    /** 提供公共静态的获取该私有类对象的方法 */
    public static LazySingletonWithDoubleCheck getInstance() {
        if (instance == null) {
            synchronized (LazySingletonWithDoubleCheck.class) {
                if (instance == null) {
                    instance = new LazySingletonWithDoubleCheck();
                }
            }
        }

        return instance;
    }

}

双重锁检查懒汉式-测试类

懒汉式4——静态内部类
/**
 * @author jiangsj
 * 懒汉式——静态内部类单例
 * 优点:
 *     (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
 *     (2) 在外部类被调用的时候内部类才会被加载,内部类要在方法调用之前初始化,巧妙地避免了线程安全问题
 *     (3) 兼顾了synchronized的性能问题
 * 缺点:
 *     反序列化,反射与克隆可破坏单例
 */
public class LazySingletonWithInnerClass {

    /** 私有化类构造器 */
    private LazySingletonWithInnerClass() {}

    /** 使用内部类定义静态私有 LazySingletonWithInnerClass 类对象 */
    private static class LazyHolder {
        private static final LazySingletonWithInnerClass INSTANCE = new LazySingletonWithInnerClass();
    }

    /** 提供公共静态的获取该私有类对象的方法 */
    public static LazySingletonWithInnerClass getInstance() {
        return LazyHolder.INSTANCE;
    }

}

静态内部类懒汉式-测试类

注册登记式

每使用一次,都往一个固定的容器中去注册并将使用过的对象进行缓存,下次去取对象的时候,就直接从缓存中取值,以保证每次获取的都是同一个对象。Spring IOC中的单例模式,就是典型的注册登记式单例。

/**
 * @author jiangsj
 * 注册登记式——map容器单例
 */
public class RegisterSingletonFromMap {

    /** 私有化类构造器 */
    private RegisterSingletonFromMap() {}

    /** 使用 ConcurrentHashMap 容器,装载 RegisterSingletonFromMap 类对象 */
    private static Map map = new ConcurrentHashMap();

    /** 提供公共静态的获取该私有类对象的方法 */
    public static RegisterSingletonFromMap getInstance() {
        String className = RegisterSingletonFromMap.class.getName();

        synchronized (RegisterSingletonFromMap.class) {
            if (!map.containsKey(className)) {
                map.put(className, new RegisterSingletonFromMap());
            }
        }

        return map.get(className);
    }

}

map容器单例-测试类

注册登记式还有一种写法,枚举型单例
/**
 * @author jiangsj
 * 注册登记式——枚举单例
 */
public class RegisterSingletonFromEnum {

    /** 私有化类构造器 */
    private RegisterSingletonFromEnum() {}

    /** 使用 enum 实例特性创建 RegisterSingletonFromEnum 类对象 */
    private enum Singleton {
        INSTANCE;

        private RegisterSingletonFromEnum instance;

        // JVM保证此方法只调用一次
        Singleton() {
            instance = new RegisterSingletonFromEnum();
        }

        public RegisterSingletonFromEnum getInstance() {
            return instance;
        }
    }

    /** 提供公共静态的获取该私有类对象的方法 */
    public static RegisterSingletonFromEnum getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

}

枚举型单例-测试类

序列化与反序列化式

序列化与反序列化式单例,需要重写readResolve()方法。

/**
 * @author jiangsj
 * 序列化与反序列化式单例
 */
public class SerializableSingleton implements Serializable {

    /** 私有化类构造器 */
    private SerializableSingleton() {}

    /** 定义静态私有类对象 */
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();

    /** 提供公共静态的获取该私有类对象的方法 */
    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }

    /** 重写readResolve()方法,保证反序列化生成对象时获得的是同一个对象 */
    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }

}

序列化与反序列化式单例-测试类

上述所有单例的测试类(序列化与反序列化式单例测试类除外)都包含如下单元测试方法:

  • concurrentGetSingletonWithCyclicBarrierTest()
  • concurrentGetSingletonWithCountDownLatchTest()
  • breakSingletonByReflectionTest()

序列化与反序列化式单例测试类包含如下测试方法:

  • serializableSingletonTest()

单例模式要点总结

  • Singleton 模式中的实例构造器可以设置为 protected 以允许子类派生。
  • Singleton 模式一般不要实现 Clone 接口,因为这有可能导致多个对象实例,与 Singleton 模式的初衷违背。
  • 如何实现多线程环境下安全的 Singleton?需注意对双检查锁的正确实现。

结束语

以上便是我对Java单例模式学习的总结,如有疏漏或错误之处,欢迎大家留言指正。

你可能感兴趣的:(Java设计模式之——单例模式)