一篇博客读懂设计模式之---单例模式

一。  单例模式

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

总体来说,单例模式应用的场景一般发现在以下条件下:

  (1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。

  (2)控制资源的情况下,方便资源之间的互相通信。如线程池等。

有以下的特点:(eg。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。为了避免不一致状态)

1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。

优缺点

优点: (频繁new同一个)
    1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例 
    2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。 
    3.提供了对唯一实例的受控访问。 
    4.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。 
    5.允许可变数目的实例。

6.避免对共享资源的多重占用。

缺点: (保存状态,扩展职责)
    1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。 
    2.由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。 
    3.单例类的职责过重,在一定程度上违背了“单一职责原则”。

4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

1)*懒汉式单例:Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
ps:以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,

//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton1 {//1、第一步先将构造方法私有化private Singleton1(){}//2、然后声明一个静态变量保存单例的引用private static Singleton1 single = null;//3、通过提供一个静态方法来获得单例的引用//不安全的public static Singleton1 getInstance(){if (single == null){single = new Singleton1();}return  single;}
}
//懒汉式单例.保证线程安全
public class Singleton2 {//1、第一步先将构造方法私有化private Singleton2(){}//2、然后声明一个静态变量保存单例的引用private static Singleton2 single = null;//3、通过提供一个静态方法来获得单例的引用//为了保证多线程环境下正确访问,给方法加上同步锁synchronized//慎用  synchronized 关键字,阻塞,性能非常低下的//加上synchronized关键字以后,对于getInstance()方法来说,它始终单线程来访问//没有充分利用上我们的计算机资源,造成资源的浪费public static synchronized Singleton2 getInstance(){if(single == null){single = new Singleton2();}return single;}
}
//懒汉式单例.双重锁检查
public class Singleton3 {//1、第一步先将构造方法私有化private Singleton3(){}//2、然后声明一个静态变量保存单例的引用private static Singleton3 single = null;//3、通过提供一个静态方法来获得单例的引用//为了保证多线程环境下的另一种实现方式,双重锁检查//性能,第一次的时候public static Singleton3 getInstance(){synchronized (Singleton3.class){if (single == null){single = new Singleton3();}return single;}}
}

2)*饿汉式单例:(饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。)


public class Singleton4 {/*** 懒汉式单例* 最常用的一种模式就是这种*///1. 私有化构造器//相当于有一个默认的public的无参的构造方法,就意味着在代码中随时都可以new出来private Singleton4(){}//2. 生成一个静态内部构造类//private 私有的保证别人不能修改//static 保证全局唯一private static class LazyLoader{//final 为了防止内部误操作,代理模式,GgLib的代理模式//静态内部类是为了防止反射获取属性private static final Singleton4 INSTANCE = new Singleton4();}//3、同样提供静态方法获取实例//final 确保别人不能覆盖public static Singleton4 getInstance(){//【静态方法】中的逻辑,是要在用户调用的时候才开始执行的//方法中实现逻辑需要分配内存,也是调用时才分配的//【区别】与静态块的最大区别return LazyLoader.INSTANCE;}
}

还有一种注册式:


//类似Spring里面的方法,将类名注册,下次从里面直接获取。
public class Singleton5 {private static Map<String,Singleton5> map = new HashMap<>();static {Singleton5 single = new Singleton5();map.put(single.getClass().getName(), single);}//静态工厂方法,返还此类惟一的实例private Singleton5(){}public static Singleton5 getInstance(String name) throws ClassNotFoundException {if(name == null){name = Singleton5.class.getName();}if(map.get(name) == null){try{map.put(name,(Singleton5)Class.forName(name).newInstance());} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}return map.get(name);}
}

3) 饿汉式和懒汉式区别(重点)

从名字上来说,饿汉和懒汉,

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

另外从以下两点再区分以下这两种方式:

1、线程安全:

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是下面的1、2、3,这三种实现在资源加载和性能方面有些区别。

2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

1. 在getInstance方法上加同步

2. 双重检查锁定:

3. 静态内部类:

<span style="color:#3333ff">public class Singleton {    private static class LazyHolder {    private static final Singleton INSTANCE = new Singleton();    }    private Singleton (){}    public static final Singleton getInstance() {    return LazyHolder.INSTANCE;    }
}    </span>

至于1、2、3这三种实现又有些区别,

第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,

第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

线程安全:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:public String getName() {  return name;  }  public void setName(String name) {  this.name = name;  }  public void printInfo() {  System.out.println("the name is " + name);  }  }  
public class TMain {  public static void main(String[] args){  TestStream ts1 = TestSingleton.getInstance();  ts1.setName("jason");  TestStream ts2 = TestSingleton.getInstance();  ts2.setName("0539");  ts1.printInfo();  ts2.printInfo();  if(ts1 == ts2){  System.out.println("创建的是同一个实例");  }else{  System.out.println("创建的不是同一个实例");  }  }
}  

结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。

对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。

一篇博客读懂设计模式之---单例模式相关推荐

  1. 教你如何一篇博客读懂设计模式之—--原型模式

    教你如何一篇博客读懂设计模式之----原型模式 what:是什么 原型模式: 用于创建重复的对象,既不用一个属性一个属性去set和get,又不影响性能,原型模式产生的对象和原有的对象不是同一个实例,他 ...

  2. 教你如何一篇博客读懂设计模式之—--工厂模式

    一篇博客读懂设计模式之-工厂模式 工厂模式在我们日常开发的时候经常用到,相信大家都有了一定的了解,工厂模式是一种创建对象的设计模式,它提供一种创建对象的最佳方式. 主要过程是: 定义一个创建对象的接口 ...

  3. 一篇博客读懂设计模式之---委派模式

    一篇博客读懂设计模式之-委派模式 委派模式可能大家听起来不太熟悉,但是在代码开发的时候却很好用,下面从几个方面来介绍一下 what:是什么? 委派模式:顾名思义,委托其他对象或者实例来帮我们完成任务, ...

  4. 一篇博客读懂设计模式之---动态代理与反射

    一篇博客读懂设计模式之---动态代理与反射 先来讲一下反射: 1 关于反射 反射最大的作用之一就在于我们可以不用在编译时就知道某个对象的类型,而在运行时通过提供完整的"包名+类名.class ...

  5. 一篇博客读懂设计模式之---模板方法模式

    设计模式之模板方法模式: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. 简而言之就是:父类定义了骨架(调用哪些方法及其 ...

  6. 一篇博客读懂设计模式之-----策略模式

    设计模式之策略模式 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的对象 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换. 主要解决:在有多种算法相似的情况下 ...

  7. 一篇博客读懂设计模式之---工厂模式

    设计模式之-工厂模式 工厂模式: 创建过程: 创建Shape接口 public interface Shape {void draw(); } 创建实现类: public class Circle i ...

  8. [入门篇]用史上最生动的方式让你一篇博客搞懂Linux进程地址空间,包看包懂!

    目录 0.前言 1.初始程序的地址空间划分 1.1程序地址空间图解 1.2程序地址空间区域划分验证 1.3 程序地址空间小补充 1.4 引入进程地址空间 *2. 两个生动的例子理解进程地址空间 2.1 ...

  9. 2022年从零开始,用一篇博客掌握 nginx 的初级配置

    本篇博客主要用于记录 nginx.conf 这一个文件如何修改的相关问题. 当 nginx 安装之后,默认的配置如下所示(数据来源为宝塔自动生成),本篇博客重点介绍的是配置虚拟机相关内容,即 serv ...

最新文章

  1. Facebook发现:计算机识别系统更青睐识别“有钱人”,准确率高出20%
  2. E:Unable to locate package libssl-dev:i386
  3. 看完这篇 HTTPS,和面试官扯皮就没问题了
  4. 数据库设计笔记——关系型数据库基础知识(三)
  5. mySQL 数据库错误
  6. js 跨域深入理解与解决方法
  7. 时空、维度,以及其他(二)
  8. Java游戏程序设计 第3章 游戏程序的基本框架
  9. js中的行为委托和无类编程
  10. 【图像去噪】基于matlab最佳加权双边滤波图像去噪【含Matlab源码 459期】
  11. 网站头像: favicon.ico
  12. 大前端-阶段2 - 模块2 - 前端工程化实战-模块化开发(ESModule)-打包工具(webpack4)
  13. logout退出登录该用get方法还是post方法?
  14. python生成exe文件
  15. FPGA:ov7725摄像头通过VGA/HDMI显示RGB565格式的图像
  16. 计算机不接受跨专业考研,2016跨专业考研需谨慎的专业解读:计算机
  17. JSONObject.toBean() 把jsonobject转换成实体类
  18. Study16 面向对象三大特性
  19. C语言主函数返回值含义
  20. 都是购买ARM授权,为何高通华为三星联发科的芯片像4个妈生的?

热门文章

  1. 游标迭代器(过滤器)——Scan
  2. OnKeyPress事件和Javascript检测键盘输入
  3. Spring整合JDBC开发
  4. 儿童编程python入门_儿童编程python入门
  5. linux 两个mysql_Linux下安装两个MySQL的方法
  6. 思科isis路由的优先级_华为 路由双点双向引入
  7. kafka集群脚本启动失败,在kafkaServer.out中提示nohup: failed to run command `java’: No such file or directory
  8. idea代码补全声明代码_用了这么多年idea,竟然不知道这些代码补全功能
  9. 初学者选黑卡还是微单_3500以内的微单相机好用吗?值得初学者入手吗?
  10. Firefox鼠标手势插件在哪安装 火狐浏览器鼠标手势怎么用