单例模式——Java

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
保证一个类仅有一个实例,并提供一个全局的访问点。
(1)构造方法私有化
(2)声明一个本类对象
(3)给外部提供一个静态方法获取对象实例。
注意
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例: 1、一个党只能有一个书记。 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
实现
我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。
SingleObject 类提供了一个静态方法,供外界获取它的静态实例。
步骤 1
创建一个 Singleton 类。

SingleObject.java
public class SingleObject {//创建 SingleObject 的一个对象private static SingleObject instance = new SingleObject();//让构造函数为 private,这样该类就不会被实例化private SingleObject(){}//获取唯一可用的对象public static SingleObject getInstance(){return instance;}public void showMessage(){System.out.println("Hello World!");}
}

步骤 2
从 singleton 类获取唯一的对象。

SingletonPatternDemo.java
public class SingletonPatternDemo {public static void main(String[] args) {//不合法的构造函数//编译时错误:构造函数 SingleObject() 是不可见的//SingleObject object = new SingleObject();//获取唯一可用的对象SingleObject object = SingleObject.getInstance();//显示消息object.showMessage();}
}

步骤 3
执行程序,输出结果:
Hello World!

单例模式的几种实现方式
单例模式的实现有多种方式,如下所示:

1、懒汉式,线程不安全
是否 Lazy 初始化:是
是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

实例

public class Singleton {  private static Singleton instance;  private Singleton (){}  public static Singleton getInstance() {  if (instance == null) {  instance = new Singleton();  }  return instance;  }  
}

接下来介绍的几种实现方式都支持多线程,但是在性能上有所差异。

2、懒汉式,线程安全
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:易
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

实例

public class Singleton {  private static Singleton instance;  private Singleton (){}  public static synchronized Singleton getInstance() {  if (instance == null) {  instance = new Singleton();  }  return instance;  }  
}

3、饿汉式
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

实例

public class Singleton {  private static Singleton instance = new Singleton();  private Singleton (){}  public static Singleton getInstance() {  return instance;  }  
}

4、双检锁/双重校验锁(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能
getInstance() 的性能对应用程序很关键。

实例

public class Singleton {  private volatile static Singleton singleton;  private Singleton (){}  public static Singleton getSingleton() {  if (singleton == null) {  synchronized (Singleton.class) {  if (singleton == null) {  singleton = new Singleton();  }}  }  return singleton;  }  
}

5、登记式/静态内部类
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

实例

public class Singleton {  private static class SingletonHolder {  private static final Singleton INSTANCE = new Singleton();  }  private Singleton (){}  public static final Singleton getInstance() {  return SingletonHolder.INSTANCE;  }  
}

6、枚举
JDK 版本:JDK1.5 起
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

实例

public enum Singleton {  INSTANCE;  public void whateverMethod() {  }  
}

经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325841.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

winform通过ListView绑定数据库数据源

来,我们开始拉窗体,和我一样的这个就可以: 很简单,在窗体里面只放一个ListView控件即可,然后点击ListView的属性Columns 分别在Text里面写用户名和密码,点击确定。 然后设置显示视图View为Details&…

理解C# 4 dynamic(4) – 让人惊艳的Clay

一,多种方式初始化对象 1, 最简单的对象构建和初始化 dynamic New new ClayFactory();var person New.Person(); person.FirstName "Louis"; person.LastName "Dejardin"; 注意这里的Person并不是一个具体的,实际存在的类或者…

java实现多文件上传至本地服务器

转载自 java实现多文件上传至本地服务器 博主最近在做一个内网项目,内部可以访问外部数据,但是外部访问不了内部数据,这也就造成了可能文件无法上传,所以博主另辟蹊径,在本地服务器上建立一个文件夹专门用来存储上传…

配置struts.xml时extends=struts-default会报错,原因和解决

提示:此种解决方法只适用于Intellij IDEA,MyEclipse或者Eclipse还得另寻它法,但估计原因应该是类似的。 在Intellij IDEA 2017使用Struts2框架时,若新建项目时并未导入Struts2框架而是在后期手动新建lib目录导入Struts2框架后&…

React生命周期(新)

三个标红的需要前面加上UNSAFE_ 三个常用的组件

《坚毅》的读后感

1.你的梦想是什么?有了梦想,那么接下来呢? 2.有什么问题? 3.你每天醒来后想阿着的是什么? 4.为什么现在是正确的时机? 5.你曾经做过的最艰难的事情是什么? 6.谁希望你成功? 7.你最重…

发力企业级市场,微软Hololens开辟了一条VR新道路

近日微软Hololens可谓是动作频频,2月份Hololens与BGC Engineering合作,从矿山规划到泥石流建模,该应用程序可帮助BGC Engineering及其客户可视化一个场景并解决工程问题。 3月份HoloLens与Cigna展开医疗合作,将Hololens用于健康检…

写给工程师的10条精进原则

转载自 写给工程师的10条精进原则 引言 时间回到8年前,我人生中的第一份实习工作,是在某互联网公司的无线搜索部做一个C工程师。当时的我可谓意气风发,想要大干一场,结果第一次上线就写了人生中第一个CaseStudy。由于对部署环境…

Struts2下创建自定义类型转换器(表单中日期的处理)

在表单提交中需要有日期的输入,默认的Struts2处理机制可能不能满足需求,需要自定义一下类型转换器。如: String----->java.util.Date:输入 java.util.Date------>String:输出:查询显示,输出错误回显 …

部署:持续集成(CI)与持续交付(CD)——《微服务设计》读书笔记

一.CI(Continuous Integration)简介 CI规则1:尽量频繁地把代码签入到分支中以进行集成 CI规则2:不光要对语法进行验,也要提供一系列的自动化来验证 CI规则3:CI失败后,要把修复CI当做第一优先级的…

使用ListView控件展示数据

一、使用ListView实现大图标小图标的功能: 1.拖一个ListView控件 2.拖两个imageList控件,并且重命名为imgBig和imgSmall,分别设置两个控件的图片和大小。 3.编辑ListView的Items,添加几项(C、D、E盘) 4.通过ListView绑…

React中的唯一标识key(用index VS id)和key的选择

1. 虚拟DOM中key的作用:1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较&#x…

Android中ImageView的scaleType 属性说明。

ImageView是Android中最常用的控件之一,在使用ImageView的时候,必不可少的会使用到scaleType属性,该属性指定了你的ImageView如何显示图片。包括是否进行缩放,等比缩放,缩放后显示的位置, Android中提供了八…

支付渠道参数如何设计成路由化配置

转载自 支付渠道参数如何设计成路由化配置 今天我们来探讨在搭建支付系统时一个比较关键的问题:渠道参数路由化配置如何设计? 在开发支付系统的时候,我们经常会涉及到对接多个支付渠道,除常见的支付宝、微信外可能还会根据不同的…

实现Windows程序的数据更新

一、枚举: 1.语法:public enum 名字{ 值1,值2} 2.eg:public enum Genders{Male,FeMale} 3.使用枚举:枚举名.值 4.枚举的好处: (1)使代码更易于维护 (2)更易于用户输入值,直接用.的方式就可以获取值 (3)使代码更清晰&a…

分布式数据访问服务之1—华山论剑篇

业界主流的互联网架构中,分布式服务框架、分布式数据访问服务、消息队列服务、服务网关(API)、分布式事务等都是核心的组件和框架。 当我们的系统规模越来越大,从几台服务器扩展到几十台、几百台、上千台,传统的烟囱式的、大集中式系统架构&…

Android中异步任务AsyncTask的使用

1、当程序启动的时候,Android同时会启动一个对应的线程,【主线程、UI线程】主要负责处理与UI相关的线程 2、UI线程不允许访问网络进行耗时操作 3、子线程不允许更新UI。 由于Android中主线程不能访问网络、子线程不能更新UI,需要线程间通讯 完…