23种设计模式:15单例模式(Singleton )

介绍

好的设计是性能的关键,能带来程序“质”的优化,比如今天的单例模式,虽然非常简单,但是项目内使用非常多。

适合场景

频繁使用的类,特别是创建需要花费大量时间或者内存的类,设置为单例模式,可以提高运行速度、同时可以减少GC压力

实现方式

单例模式的实现方式比较多,今天介绍主要的5种,以及他们的优缺点:恶汉模式、懒汉模式、内部类实现、枚举实现以及反射实现单例

恶汉模式

public class Singleton {
	private Singleton() {
		//创建单例的过程可能会比较慢
		System.out.println("Singleton is create");
	}
	private static Singleton instance = new Singleton();
	public static Singleton getInstance() {
		return instance;
	}
}
  • 缺点是在系统启动的时候就加载,增加了系统启动时间,同时过早的消耗了内存
  • 但如果类本身就需要在系统启动后立即使用,推荐使用此模式

懒汉模式(双锁机制 + volatile)

public class LazySingleton {
    private LazySingleton(){
        //创建单例的过程可能会比较慢
        System.out.println("LazySingleton is create");
    }
    private static LazySingleton instance = null;
    public static LazySingleton getInstance() { 
        if (instance == null){  //代码 3
            synchronized(LazySingleton.class){ //这里同步其他对象也可以  代码 2
                if(instance == null){ //代码1
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

  • 如果类创建非常耗时的时候,可以做延迟加载,提高系统启动速度
  • 同时为了防止多线程,多次创建“LazySingleton ”类,增加双锁机制 + volatile,从而解决每次访问需要加锁的效率
  • 同步代码块在代码里面判断里面,所以不会影响之后调用的效率,如非第一次和第二次调用存在竞争关系,synchronized是偏向锁,也不会影响效率,所以懒汉模式获取对象的效率极高
双锁机制+volatile

这里额外的提一下,为啥要用双锁机制+volatile

  • 双锁机制,可以防止第一次访问的代码运行在代码1和代码3之间时,而第二次及之后的访问的代码已经运行了代码3,处于等待锁的情况。如果没有代码1的第二次校验,类将会创建多次
  • volatile的作用呢?那就是更小的错误概率了,主要是类的创建过程非原子性,高并发情况下可能出现指令重排序,其它线程可能会拿到属性为空的对象,而误认为已经已经创建完成,而volatile可以防止代码重排序。

内部类实现

public class StaticSingleton {

	private StaticSingleton(){
		System.out.println("StaticSingleton is create");
	}
	private static class SingletonHolder {
		private static StaticSingleton instance = new StaticSingleton();
	}

	public static StaticSingleton getInstance() {
		return SingletonHolder.instance;
	}
}
  • 将类加载放在内部类,既可以延迟加载类,又可以防止多线程多次创建类
  • 缺点:使用反射,可以强行调用私有构造方法。如果使用反序列化得到的不是同一个类,违背了单例模式的原则
优化反序列化问题
  • 序列化是,往内存地写再从内存里读出而"组装"成一个跟原来一模一样的对象,但可能不是用一个对象了
  • 所以不能保证单列,所以我们需要单独处理,在对象中添加如下的一个方法
//这样当JVM从内存中反序列化地"组装"一个新对象时,就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.
protected Object readResolve(){
    return MyObjectHandler.myObject;
}

枚举实现

public enum Singleton {
    INSTANCE;
    public void introduction() {
        System.out.println("This is a singleton pattern about enumeration");
    }
}
   public static void main(String[] args) {
        Singleton.INSTANCE.introduction();
    }
  • 利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的。
  • 枚举可以防止反序列化反射创建对象,从而保证了对象的唯一性

反射实现

  • 创建了一个private的构造方法的对象,因此不能通过new的方式获得对象
class Singleton {
	private Singleton() {
		System.out.println("****** Singleton类的构造方法 ******");
	}
	public void print() {
		System.out.println("Bonjour~Bridge");
	}
}
sun.misc.Unsafe类

可以利用反射来获取对象,直接利用C++来代替jvm执行,即:可以绕过JVM的管理机制,如果一旦使用了Unsafe,就不能用上JVM的内存管理机制以及垃圾回收处理

列表 1列表 2
构造方法private Unsafe() {}
私有常量private static final Unsafe theUnsafe = new Unsafe();
  • 从上面可知,要强获得Unsafe对象,就必须用反射机制
	public static void main(String[] args) throws Exception {
		Field field = Unsafe.class.getDeclaredField("theUnsafe") ; // 获取成员
		field.setAccessible(true); // 解除封装
		// 这里以及获得一个Unsafe对象了
		Unsafe unsafeObject = (Unsafe) field.get(null) ; //原本的Field需要接收实例化对象,但是Unsafe不需要
		//下面我要利用Unsafe获得一个Singleton对象
		Singleton instance = (Singleton) unsafeObject.allocateInstance(Singleton.class) ; // 获取对象,不受JVM控制
		instance.print();// 调用类中的方法
	} 
  • 上面Unsafe绕过了JVM实例化的管理,上面有增加了一种单例模式的实现方法,但是这种方法不建议使用,只是提供给大家一个学习的参考

总结

  • 项目中用的最多还是双重校验+volatile的懒汉模式
  • 另外很多博主和书集都支持使用枚举的方式实现,因为他足够安全,且保证了对象的唯一
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值