并发编程-单例

x33g5p2x  于2022-07-04 转载在 其他  
字(3.6k)|赞(0)|评价(0)|浏览(565)

并发编程-单例

说在前面的话

今天的文章很短,但是很经典,值得你仔细阅读每一个文字…

正如我开篇所说,我们要整理一些java并发编程的学习文档,这一篇就是第八篇:单例模式。这一篇主要聊聊单例的几种实现方式已经适用的场景。

开整

稍微理解一下

专业解释:单例就是确保一个类只有一个实例,并且有一个全局获取这个实例的访问点。

简单的说呢就是一个类只能创建一个实例,任何时候使用这个类的实例对象都是同一个。

基本都是了减少这个类对象创建和销毁过程的消耗。

嗯!

思考思考,一个类如果只有一个实例,必然不能随便创建,所以单例类的关键代码就是构造方法是私有的,不允许在其他地方随便创建。

单例的优点非常明确:因为只有一个实例,所以减少了内存的开销,尤其是创建和销毁的时候减少了资源浪费。避免了对资源的多重占用。

单例的缺点呢:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

单例的使用场景

  • 连接池:一个系统中一个数据源的连接池往往都是一个实例。
  • 线程池:同上。
  • 计数器:不用每次刷新都在数据库里加一次,用单例先缓存起来
  • 创建一次消耗的资源过多的类对象。

单例的实现

单例有7种实现方式

方式1:线程不安全的懒汉模式

直接上菜:

  • 构造方法私有化
  • 提供静态的私有的本类实例对象作为成员变量
  • 提供公共的静态的获取本类对象的方法
  1. /**
  2. * @author 戴着假发的程序员
  3. */
  4. public class Singleton {
  5. // 构造方法私有化
  6. private Singleton(){}
  7. // 私有静态当前类对象作为成员变量
  8. private static Singleton instance;
  9. // 静态的公共的可以获取当前类实例对象的方法
  10. public static Singleton getInstance(){
  11. // 判断instance是否存在,如果存在就直接返回,如果不存在就创建
  12. if(instance == null){
  13. instance = new Singleton();
  14. }
  15. return instance;
  16. }
  17. }

上面的程序非常简单:如果要获取Singleton类的实例对象可以这样写Singleton.getInstance()

你会发现:

  1. Singleton s1 = Singleton.getInstance();
  2. Singleton s2 = Singleton.getInstance();
  3. System.out.println(s1 == s2);// true 额。。。。。多执行几次,有可能也会是false

说明:

  • 这个方式一开始并没有创建instance,在第一次使用的时候才会创建,这才是懒汉模式的真谛。有效的实现了延迟加载。节省了内存。

  • 这个方式不是线程安全的,可能会有隐患。

  • 如果线程A调用getInstance方法,判断了instance为null,正准备创建对象,结果CPU不给资源了,于是就稍微停了一会,这是线程B也调用了getInstance方法,结果判断instance依然是null,于是就创建了instance对象。之后线程A开始执行,线程A不会再判断了,直接创建instance对象,这样的话这个类的对象就被创建了两次。

方式2:线程安全的懒汉模式

上菜:

  • 构造方法私有化
  • 提供静态的私有的本类实例对象作为成员变量
  • 提供公共的静态的同步的获取本类对象的方法
  1. /**
  2. * @author 戴着假发的程序员
  3. */
  4. public class Singleton1 {
  5. // 构造方法私有化
  6. private Singleton1(){}
  7. // 私有静态当前类对象作为成员变量
  8. private static Singleton1 instance;
  9. // 静态的公共的可以获取当前类实例对象的方法
  10. public static synchronized Singleton1 getInstance(){
  11. // 判断instance是否存在,如果存在就直接返回,如果不存在就创建
  12. if(instance == null){
  13. instance = new Singleton1();
  14. }
  15. return instance;
  16. }
  17. }

这个测试吗很简单:

  1. Singleton s1 = Singleton.getInstance();
  2. Singleton s2 = Singleton.getInstance();
  3. System.out.println(s1 == s2);// true 额。。。这里无论执行多少次都是true

说明:

  • 这种方式很明显就是在getInstance的方法上增加synchronized修饰符,这样就成功避免了线程安全的问题。
  • 但是我们都知道一旦加锁了,程序效率就会略有降低(不过呢,在JDK1.6之后synchronized做了优化,如果没有大量并发的情况下其实效率影响也不大)
方式3:饿汉模式

饿汉模式关键在这个“饿”字。额… 记得以前我吃饭吃的很着急的时候,我亲爱的老母亲就说:“慢点吃,搞得饿死鬼上身似得”。非常庆幸的是老母亲现在也会这样说我。哈哈哈哈哈。。。。

言归正传:饿汉模式就是在一开始的时候直接创建实例对象

  • 构造方法私有化
  • 提供静态的私有的本类实例对象作为成员变量,并且直接实例化
  • 提供公共的静态的获取本类对象的方法
  1. /**
  2. * @author 戴着假发的程序员
  3. */
  4. public class Singleton2 {
  5. // 构造方法私有化
  6. private Singleton2(){}
  7. // 私有静态当前类对象作为成员变量
  8. private static Singleton2 instance = new Singleton2();
  9. // 静态的公共的可以获取当前类实例对象的方法
  10. public static synchronized Singleton2 getInstance(){
  11. // 判断instance是否存在,如果存在就直接返回,如果不存在就创建
  12. if(instance == null){
  13. instance = new Singleton2();
  14. }
  15. return instance;
  16. }
  17. }

饿汉模式真的有点饿,一开始就创建了。

但是如果,现在不饿呢,直接把饭煮了是不是会有点浪费。

所以饿汉模式的问题就是如果一开始就把类对象创建好了, 但是这个对象长时间用不到,那么就是有些浪费资源了。

所以呀,还是要看实际的使用场景的。

方式4:双检锁/双重校验锁

程序,必须追求效率和省资源。所以我们希望单利模式中创建对象的方法也是效率高高滴。

但是我们一旦给方法加锁必然降低方法的执行效率,所以双重校验锁可能是一个不错的选择,既能能相对的提高程序效率,又能保证线程安全。

具体的实现就是:

  1. /**
  2. * @author 戴着假发的程序员
  3. */
  4. public class Singleton3 {
  5. // 构造方法私有化
  6. private Singleton3(){}
  7. // 私有静态当前类对象作为成员变量
  8. private static Singleton3 instance;
  9. // 静态的公共的可以获取当前类实例对象的方法
  10. public static Singleton3 getInstance(){
  11. // 判断instance是否存在,如果存在就直接返回,如果不存在就创建
  12. if (instance==null) {
  13. // 上锁
  14. synchronized (Singleton3.class) {
  15. // 再次判断instance是否存在,如果存在就直接返回,如果不存在就创建
  16. if (instance == null) {
  17. instance = new Singleton3();
  18. }
  19. }
  20. }
  21. return instance;
  22. }
  23. }
方式5:静态内部类

静态内部类的实现方式也叫登记式。是个啥情况呢:

直接上菜:

  1. /**
  2. * @author 戴着假发的程序员
  3. */
  4. public class Singleton4 {
  5. // 构造方法私有化
  6. private Singleton4(){}
  7. // 准备一个静态内部类
  8. private static class SingletonHolder{
  9. // 申明并且实例化一个外部单利类的实例对象,并且设置为常量。
  10. private final static Singleton4 INSTANCE = new Singleton4();
  11. }
  12. // 静态的公共的可以获取当前类实例对象的方法
  13. public static Singleton4 getInstance(){
  14. // 直接返回内部类的成员常量
  15. return SingletonHolder.INSTANCE;
  16. }
  17. }

这种方式有啥用?

  • 在没有调用getInstance方法之前,静态内部了会延迟加载也就是对象的创建会延迟。
  • 利用classloader的机制确保了在创建实例是肯定是单线程的。
  • 当然了如果你不希望实例对象延迟加载那还是使用饿汉模式比较好。
方式6:枚举

这个方式实在太简单,也好理解(当然你首先要知道啥是枚举)。所以就不多做解释了。

  1. public enum Singleton {
  2. INSTANCE;
  3. public void whateverMethod() {
  4. }
  5. }

关于单利模式的情况就是这样了。

还有其他的并发编程相关的内容,我会持续更新,欢迎关注。

我是”起点编程“的"戴着假发的程序员" 欢迎关注…欢迎评论。。。。。

起点编程-是你我的未来…

相关文章