单例模式可以保证系统中,应用该模式的类只有一个对象实例
-
使用场景
- spirng的ioc中的对象
- springboot中的controller,service,dao层中通过@Autowried的依赖注入对象默认都是单例的
-
分类
- 懒汉:延迟创建对象
- 饿汉:提前创建对象
-
实现步骤
- 1、私有化构造函数
- 2、提供获取单例的方法,统一返回对象
-
接下来就直接进入代码啦,先演示的是懒汉模式
- 先创建一个类SingletonLazy.class
public class SingletonLazy{
// 实例属性
private static SingletonLazy instance;
//私有化实例方法
private SingletonLazy(){};
//提供方法返回对象
public static SingletonLazy getInstance1(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
}
- 上面这种方法在多线程的情况是不安全的,所以我们应该改为线程安全的
public static synchronized SingletonLazy getInstance2(){
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
- 上面这种方法虽然保证了线程安全,但是在大量线程并发的情况,性能会比较差,所以我们可以尝试先缩小锁的范围。
public static SingletonLazy getInstance3(){
if(instance == null){
//锁住当前对象
synchronized(SingletonLazy.class){
instance = new SingletonLazy();
}
}
return instance;
}
- 但是你以为上面的就是没有问题了吗,其实你仔细分析的话,会发现上面虽然缩小了锁的范围,但是并没有能完全保证线程安全,在极端的情况下,假设abc三个线程同时执行完了
if(isntance == null){
这串代码,那么接下来就会发生new了三次对象了。所以我们可以加入双重检查锁定。
public static SingletonLazy getInstance4(){
if(instance == null){
//锁住当前对象
synchronized(SingletonLazy.class){
if(instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
- 到这里,其实看起来就已经很完美了是不?但是不要以为稳了,这里面还是并非安全的,我们知道java中实例化一个对象要经过大概三个步骤
- 1、申请内存空间
- 2、在空间内创建对象
- 3、将对象地址赋值给引用 instance
- 这里面设计到一个问题–JVM指令重排,不懂的可以先百度,2跟3步骤不是保证顺序的,也就是说可能执行顺序为 1>3>2,这个时候我们的代码就可能会出现new出来的对象是会有问题的(有引用了,但是对象还没真正在空间中创建完成),也就是说其他线程可能用了非完整的对象,这样可能会引发大问题。所以我们这里可以加入volatile关键字,可以禁止指令重排
public static volatile SingletonLazy instance;
public static SingletonLazy getInstance5(){
if(instance == null){
synchronized(SingletonLazy.class){
if(instance == null){
instance = new SingletonLazy();
}
}
}
return instance;
}
- 饿汉模式
- 创建一个SingletonHungry.class
public class SingletonHungry{
//饿汉模式当然是直接实例化对象啦
private static SingletonHungry instance = new SingletonHungry();
//私有化构造方法
private SingletonHungry(){};
//返回对象的方法
public static getInstance(){
return instance;
}
}
- jdk中有哪些用到了单例模式
- Runtime类(检查jvm启动相关内存使用等的类)
总结:
-
实战中如何选择
-
饿汉实现非常简单,也没有安全问题,但是类加载的时候就创建对象,如果是对象非常大和耗时复杂的,就不建议这种方式。其他情况建议采用懒汉模式
-
更多原创内容,请访问www.渣男.cn