设计模式——[单例模式]
1. 简单说明
设计模式有23种,那其中又分为以下三大型模式,这三大型中每个又包含一些模式。那么今天我们首先来讲一下创建型模式中的单例模式。
在文章最后可关注作者公众号:蚕豆是个程序猿
回复 设计模式/面试题 即可获得相应的学习资料,面试题及设计模式电子书。
2. 什么是单例模式
1.借用百度百科一句话:单例模式属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
2.借用大话设计模式这本书中一句话: 保证一个类仅有一个实例,并提供一个访问他的全局访问点。
那看完这些介绍这个时候我们自己再想想,什么是单例模式?
那个人理解通俗点来说,单例模式属于设计模式中的一个模式,他是一种思想。为解决程序上带来的问题,而引出的一种模式。那单例模式本身展现的"技能"就是 保证一个类仅仅存在一个实例,和一个全局访问的点。
同志们注意:学习不能cv,通过理解转成自己的。哪怕自己的组织并不是那么完美。
3. 单例模式能干什么
说单例模式能干什么,那其实就是说单例模式的作用/和它的用途场景。
由上述的概论我们也大概知道,单例模式的一些作用。对的!
单例模式 不是仅能存在一个实例吗,那这样避免了资源浪费,节省了内存消耗呀。同时我们还能通过全局访问点访问。细思极恐呀!
4.单例模式方式
1. 饿汉式模式
1.单例模式的饿汉式(静态常量)
创建的步骤:
- 创建一个私有的构造器
- 实例化一个私有静态实例
- 提供全局访问点(公有)
代码:
public class SingleTest(){ //私有的构造器 private SingleTest(){ } //实例化一个私有静态 private static final SingleTest sing=new SingleTest(); //公有的全局访问点方法 public static SingleTest getSingle(){ return sing } }
介绍:
结论: 此方式是可以使用滴。在类装载的时候就完成了初始化避免了线程同步的问题,也就是说他是线程安全的。但是呢它并没有达到懒加载(Lazy Loading)的效果所以可能会造成内存的浪费。
1.单例模式的饿汉式(静态代码块)
创建的步骤:
- 创建一个私有的构造器
- 一个私有静态类变量
- 静态代码块
- 提供全局访问点(公有)
代码:
public class SingleTest(){ //私有的构造器 private SingleTest(){ } //私有的静态类变量 private static SingleTest sing; //实例化一个私有静态 static { sing=new SingleTest(); } //公有的全局访问点方法 public static SingleTest getSingle(){ return sing } }
介绍:
结论: 此方式和静态常量模式 类似。
2. 懒汉式模式
1.单例模式的懒汉式(线程不安全普通模式)
创建的步骤:
- 创建一个私有的构造器
- 一个私有静态类变量
- 提供全局访问点(公有)
代码:
public class SingleTest(){ //私有的构造器 private SingleTest(){ } //一个私有静态 private static SingleTest sing; //公有的全局访问点方法 public static SingleTest getSingle(){ if(sing==null){ sing=new SingleTest(); } return sing } }
介绍:
结论: 此方式是由 懒加载(Lazy Loading) 效果,但是呢它本身会有线程安全的问题。在if(sing==null) 这句代码,假如两个 线程a,b当a线程进去这个判断但是还没往下执行 b又进来了。那这样 实例不能保证仅有一个实例。也就是线程不安全的问题。不推荐使用。
1.单例模式的懒汉式(线程安全模式)
创建的步骤:
- 创建一个私有的构造器
- 一个私有静态类变量
- 提供全局访问点(公有并且加上同步 synchronized)
代码:
public class SingleTest(){ //私有的构造器 private SingleTest(){ } //一个私有静态 private static SingleTest sing; //公有的全局访问点方法 public static synchronized SingleTest getSingle(){ if(sing==null){ sing=new SingleTest(); } return sing } }
介绍:
结论: 此方式是由 懒加载(Lazy Loading) 效果,也同样是线程安全的。但是 大家都知道 == synchronized==这同步 每次线程获取公有方法,那都要进行同步这样导致效率大大降低。不推荐使用。
扩展:
有的文章会有如下这样的改造:public class SingleTest(){ //私有的构造器 private SingleTest(){ } //一个私有静态 private static SingleTest sing; //公有的全局访问点方法 public static SingleTest getSingle(){ if(sing==null){ synchronized (SingleTest.class){ sing=new SingleTest(); } } return sing } }
这种方式其实并不能起到一个线程安全的作用,还是一样的问题线程如果进入if()判断还没执行下面,又有一个线程进入了判断。最后还不是会出现多实例线程不安全的问题。
3. 单例模式(双重检索/检查模式)
1.双重检查单例模式**(线程安全并效率可观)**
创建的步骤:
- 创建一个私有的构造器
- 一个私有静态volatile类变量
- 提供全局访问点(公有)
代码:
public class SingleTest(){ //私有的构造器 private SingleTest(){ } //一个私有静态 private static volatile SingleTest sing; //公有的全局访问点方法 public static SingleTest getSingle(){ if(sing==null){ synchronized (SingleTest.class){ if(sing==null){ sing=new SingleTest(); } } } return sing } }
介绍:
结论: 此方式推荐使用,解决了线程安全,又解决了懒加载及效率的问题。当我们的线程 a,b 到synchronized 这块后,若a先进去了那创建完一个实例,欸!当b进去后发现 sing不等于null了。那以后更为方便在第一个if判断那就直接return 因为已经不等于null了。
4. 单例模式(静态内部类模式)
1.静态内部类单例模式**(线程安全/懒加载效果)**
创建的步骤:
- 创建一个私有的构造器
- 一个私有静态内部类里面实例化对象
- 提供全局访问点(公有)
代码:
public class SingleTest(){ //私有的构造器 private SingleTest(){ } //一个私有静态 private static class SingleTestInstnce(){ private static final SingleTest sing=new SingleTest(); } //公有的全局访问点方法 public static SingleTest getSingle(){ return SingleTestInstnce.sing } }
介绍:
结论: 当我们的最外的类进行类装载的时候静态内部类是不会立即类装载的,而当我们使用公有方法 里面调用内部类方法时,静态内部类进行装载,当然类装载是线程安全的而且只装载一次。推荐使用。
5.创建方式总结
介绍了七种 创建的方式,每一种都有自己优缺点。那对于实际项目的使用还要谨慎选择。对于b站韩老师讲的还有一种枚举方式在这里不做介绍有兴趣自行查看呦,如下方式创建:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全普通模式)
- 懒汉式(线程安全模式)
- 懒汉式(同步代码块)
- 双重检索模式(线程安全)
- 静态内部类(线程安全)
6.简单看看框架/java具体源码用到哪些单例模式
第一
人人熟知的Spring中也用到 了单例模式
在Spring中的Bean默认的作用域就是singleton单例的,如下getSingleton源码方法代码:protected Object getSingleton(String beanName, boolean >allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
看的出来使用的什么单例模式了吧。bingo~~~就是双重检索
第二
人人熟知的java.lang包下Runtime中也用到 了单例模式public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {}
看的出来使用的什么单例模式了吧。bingo~~~就是饿汉式
到这里设计模式之单例模式到这就结束了,后期会慢慢出后面的一些模式。写的不对之处一定请大家指出谢谢~~~写写文章对记忆好一点 同时大家可以关注本人的 公众号: 蚕豆是个程序猿 回复 设计模式或面试题 获取相关资料。