单例模式(八种方法分析)

单例模式

所谓的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这时就会使用单例模式。

单例模式有八种方式

1、饿汉式(静态常量)

静态常量在类加载阶段的准备阶段完成分配空间,赋值在初始化阶段完成
补充JVM类加载知识:

  • static 变量分配空间和赋值是两个部分,分配空间在准备阶段完成,赋值在初始化阶段完成
  • 如果 static 变量是final 的基本类型或字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
  • 如果 static 变量是final 的,但属于引用类型,那么赋值也会在初始化阶段完成
/**
 * 单例模式(饿汉式-静态变量)
 * 1、构造器私有化
 * 2、类内部创建对象实例
 * 3、对外暴露一个静态的公共方法
 */
public class Singleton1 {
    // 1、构造器私有化,外部不能new
    private Singleton1() {}
    // 2、类内部创建对象实例
    private final static Singleton1 instance = new Singleton1();
    // 3、对外暴露一个静态的公共方法,返回实例对象
    public static Singleton1 getInstance() {
        return instance;
    }
}

2、饿汉式(静态代码块)

静态常量和静态代码块原理相同,实现形式不同

/**
 * 单例模式(饿汉式-静态代码块)
 */
public class Singleton2 {
    // 1、构造器私有化,外部不能new
    private Singleton2() {}
    // 2、类内部创建对象实例
    private static Singleton2 instance;
    static {
        instance = new Singleton2();
    }
    // 3、对外暴露一个静态的公共方法,返回实例对象
    public static Singleton2 getInstance() {
        return instance;
    }
}

3、懒汉式(线程不安全)

此方法线程不安全,在多线程环境下,进入到if (instance == null) 语句内的线程可能有多个,此时Singleton1类会被实例化多次

/**
 * 单例模式(懒汉式-线程不安全)
 */
public class Singleton1 {
    private static Singleton1 instance;
    private Singleton1() {}
    // 提供一个静态公有方法,当使用到该方法时,才去创建 instance
    public static Singleton1 getInstance() {
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
}

4、懒汉式(线程安全,同步方法)效率低

static getInstance()方法加synchronized 关键字,锁加在类上,每个访问该方法的线程竞争同一把锁,同一时刻只有一个线程能进入这个方法中,并完成对象创建,方法执行完后释放锁,下一个获取锁的线程进入方法后instance 已经被创建,直接返回instance
但方法级别的synchronized 锁的粒度大,效率很低,不推荐使用

/**
 * 单例模式(懒汉式-线程安全,同步方法)效率低
 */
class Singleton2 {
    private static Singleton2 instance;
    private Singleton2() {}
    // 提供一个静态公有方法,当使用到该方法时,才去创建 instance
    public synchronized static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

5、懒汉式(线程不安全,同步代码块)错误!不能使用

和懒汉式(线程安全,同步方法)相似,但使用synchronized 关键字加在if判断语句内部,实际上它是线程不安全的
有人会说将synchronized 关键字加在if语句外部,这和加到方法上基本是一样的,效率依旧很低

/**
 * 单例模式(懒汉式-同步代码块)错误!不能使用
 */
class Singleton3 {
    private static Singleton3 instance;
    private Singleton3() {}
    // 提供一个静态公有方法,当使用到该方法时,才去创建 instance
    public static Singleton3 getInstance() {
        if (instance == null) {
	        synchronized (Singleton3.class) {
				instance = new Singleton3();
			}
        }
        return instance;
    }
}

6、双重检查 *推荐使用

volatile是Java虚拟机提供的轻量级的同步机制,它有3个特性:

  • 1、保证可见性:变量改变值后能即时让其他阻塞在synchronized的线程发现,从而跳过内部的if循环
  • 2、不保证原子性:为整个对象创建的过程不被其他线程打断,需要加入synchronized锁
  • 3、禁止指令重排:

对象的创建分为三部(instance = new Singleton1();)

//1:分配对象的内存空间
memory = allocate();
//2:初始化对象
ctorInstance(memory);  
//3:设置instance指向刚分配的内存地址
instance = memory;  

即编译器或处理器为提高性能改变代码执行顺序,如果执行顺序为132,对象分配了地址,但并未初始化,其他线程拿到的instance 就是有问题的,volatile关键字禁止指令重排可解决这一问题

/**
 * 单例模式(双重检查)
 *
 * volatile是Java虚拟机提供的轻量级的同步机制,它有3个特性:
 * 1、保证可见性
 * 2、不保证原子性
 * 3、禁止指令重排
 */
public class Singleton1 {
    private static volatile Singleton1 instance;
    private Singleton1() {}
    // 提供一个静态公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
    public static Singleton1 getInstance() {
        if (instance == null) {
            synchronized (Singleton1.class) {
                if (instance == null) {
                    instance = new Singleton1();
                }
            }
        }
        return instance;
    }
}

7、静态内部类 *推荐使用

静态内部类在外部类被装载时不会被立即装载,而是在被第一次调用时才会装载,类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类的初始化时,别的线程是无法进入的。

/**
 * 单例模式(静态内部类)
 *
 * 优点:避免了线程不安全,利用静态内部类的特点实现延迟加载,效率高
 * 结论:推荐使用
 */
public class Singleton1 {
    private Singleton1() {}
    private static class SingletonInstance {
        private static final Singleton1 INSTANCE = new Singleton1();
    }
    public static Singleton1 getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

8、枚举 *推荐使用

借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重写创建新的对象。

/**
 * 结论:推荐使用
 *
 * 枚举的本质是一个类,继承了 Enum,并修饰 final表示不能被别的类继承
 * 枚举的成员变量实际上是自身的一个实例,并且是static final修饰的
 * 在静态代码块种被实例化,在类加载阶段执行,所以是饿汉式
 *
 * public final class Singleton1 extends Enum {
 *     public static final Singleton1 INSTANCE;
 *     static {
 *         INSTANCE = new Singleton1("APPLE", 0, 1);
 *         $VALUES = (new Singleton1[] {INSTANCE});
 *     }
 * }
 *
 */
public enum Singleton1 {
    INSTANCE;
    // 枚举本质也是类,也可以定义成员方法
    public void sayOk() {
        System.out.println("ok");
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是对几种常见单例模式的优缺点进行分析: 1. 饿汉式(Eager Initialization): 优点: - 简单直观,线程安全。 - 在程序启动时就创建实例,避免了多线程并发访问的问题。 缺点: - 可能会导致资源浪费,因为实例在程序启动时就创建,即使后续没有使用也会占用一定的内存空间。 2. 懒汉式(Lazy Initialization): 优点: - 节省了资源,只有在需要时才会创建实例。 缺点: - 需要处理多线程并发访问的问题,可能导致线程不安全。 - 需要使用同步机制(如锁)来保证线程安全,可能影响性能。 3. 双重检查锁(Double-Checked Locking): 优点: - 延迟加载,节省了资源。 - 在多线程环境下保证了性能,只有第一次创建实例时需要同步。 缺点: - 实现较为复杂,需要考虑多线程并发访问的细节。 - 对于早期的编译器和处理器可能会出现问题。 4. 静态内部类(Static Inner Class): 优点: - 延迟加载,节省了资源。 - 线程安全,由 JVM 在加载类时保证了线程安全性。 缺点: - 实现稍微复杂一些。 5. 枚举(Enum): 优点: - 简单直观,线程安全。 - 能够防止反射和序列化等机制对单例的破坏。 缺点: - 不支持延迟加载,即使不使用也会被实例化。 需要根据具体的需求和场景选择合适的单例模式实现方式。每种实现方式都有其优缺点,需要权衡各种因素来选择最适合的方式。如果需要考虑并发访问、延迟加载、资源消耗等方面的问题,可以综合评估不同的实现方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值