单例模式
概述
单例模式是 Java 比较简单,也是最基础,最常用的设计模式之一。在运行期间,保证某个类只创建一个实例,保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式主要有饿汉式单例、懒汉式单例、静态内部类实现单例、枚举类实现单例等,不同的方式有不同的优缺点,下面介绍各个实现方式和优缺点。
饿汉式单例
创建饿汉式单例简单粗暴,在类被虚拟机加载时就创建类对象;
缺点:可能在还不需要此实例的时候就已经把实例创建出来了,没起到lazy loading的效果;
优点:实现简单,而且安全可靠;
package pattern.single; /** * 饿汉式单例模式 不存在线程安全问题,因为在类加载时就创建好了对象 * @author ningbeibei */ public class HungrySingle { // 当类加载时创建对象 private static HungrySingle hungery = new HungrySingle(); // 私有化构造函数,屏蔽外部创建对象 private HungrySingle() { } // 提供全局访问点,直接返回实例对象 public static HungrySingle getHungrySingle() { return hungery; } }
测试类代码
package pattern.single; /** * 饿汉式单例模式测试类 * @author ningbeibei */ public class test { public static void main(String[] args) { //饿汉式单例模式,获取 HungrySingle 实例 HungrySingle hungry = HungrySingle.getHungrySingle(); System.out.println("HungrySingle对象:"+hungry); } }
运行结果
懒汉式单例
相比饿汉式,懒汉式实现了用则创建不用则不创建,真正实现了懒加载效果;
package pattern.single; /** * 懒汉式单例模式 * @author ningbeibei */ public class SinglePattern { //声明对象变量 private static SinglePattern single = null; //私有化构造函数 private SinglePattern(){ } //对外提供获取对象得方法 public static SinglePattern getSinglePattern() { if(single==null){ return new SinglePattern(); } return single; } }
注意:上面代码实现了懒汉式单例,getSinglePattern()方法先判断实例是否为空再决定是否去创建实例,看起来似乎很完美,但是存在线程安全问题。在并发获取实例的时候,可能会存在构建了多个实例的情况。所以,需要对此代码进行下改进,确保实例唯一。
改进后代码
package pattern.single; /** * 双重检查加锁 * 线程安全懒汉模式 * @author ningbeibei */ public class LockSinglePattern { //声明变量, 使用volatile关键字确保绝对线程安全 private volatile static LockSinglePattern lockSingle =null; //私有化构造函数 private LockSinglePattern() { } //提供全局唯一获取实例方法 public static LockSinglePattern getLockSinglePattern() { //判断实例是否null if(lockSingle==null) { //对单例类进行加锁 synchronized (LockSinglePattern.class) { //在判断是否为null if(lockSingle==null) { //创建实例 lockSingle = new LockSinglePattern(); } } } //返回实例 return lockSingle; } }
注意:这里采用了双重校验的方式,对懒汉式单例模式做了线程安全处理。通过加锁,可以保证同时只有一个线程走到第二个判空代码中去,这样保证了只创建 一个实例。这里还用到了volatile关键字来修饰lockSingle,其最关键的作用是防止指令重排。
测试类
package pattern.single; /** * 懒汉式单例模式测试类 * @author ningbeibei */ public class test { public static void main(String[] args) { //双重加锁,线程安全懒汉式单例模式 LockSinglePattern lock= LockSinglePattern.getLockSinglePattern(); System.out.println("线程安全懒汉式单例模式:"+lock); } }
运行结果
静态内部类
通过静态内部类的方式实现单例模式是线程安全的,因为内部静态类只会被加载一次,故该实现方式是线程安全的
代码如下:
package pattern.single; /** * 静态内部类实现单例模式 * @author ningbeibei */ public class InteriorSingle { /** * 静态内部类 * @author ningbeibei */ private static class Insingle { //静态初始化器,由jvm来报证线程安全 private static InteriorSingle single = new InteriorSingle(); } //私有化构造函数 private InteriorSingle() { } //提供全局唯一访问点 public static InteriorSingle getSingle() { return Insingle.single; } }
注意:通过静态内部类的方式实现单例模式是线程安全的,同时静态内部类不会在InteriorSingle类加载时就加载,而是在调用getSingle()方法时才进行加载,达到了懒加载的效果。似乎静态内部类看起来已经是最完美的方法了,其实不是,可能还存在反射攻击或者反序列化攻击。
测试类
package pattern.single; /** * 懒汉式单例模式测试类 * @author ningbeibei */ public class test { public static void main(String[] args) throws Exception { //静态内部类实现单例模式。这个也式线程安全得 InteriorSingle Interior = InteriorSingle.getSingle(); System.out.println("静态内部类单例模式:"+Interior); } }
运行结果
枚举实现单例模式
最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。而且写法还特别简单。
代码如下
package pattern.single; /** * 枚举类 * @author ningbeibei */ public enum TypeSingle { // 定义一个枚举的元素,它 就代表了Singleton的一个实例 TYPESINGLE; //业务方法 public void get() { System.out.println("枚举中的方法"); } }
测试代码
package pattern.single; /** * 懒汉式单例模式测试类 * @author ningbeibei */ public class test { public static void main(String[] args) throws Exception { //枚举实现单例模式 TypeSingle.TYPESINGLE.get(); } }
运行结果
破坏单例的两种方式
反射破坏单例;
序列化破坏单例;
这两中破坏单例模式的方式不会对枚举单例方式构成威胁,所以一般都推荐枚举实现单例模式;
总结
以上列举了多种单例模式的写法,在不同的场景中有不同应用;
写的不足之处还望指正,以便我及时更正避免读者误解;