3.1 概述
如果要保证系统里一个类最多只能存在一个实例时,我们就需要单例模式。这种情况在我们应用中经常碰到,例如缓存池,数据库连接池,线程池,一些应用服务实例等。在多线程环境中,为了保证实例的唯一性其实并不简单,这章将和读者一起探讨如何实现单例模式。
3.2 最简单的单例
为了限制该类的对象被随意地创建,我们保证该类构造方法是私有的,这样外部类就无法创建该类型的对象了;另外,为了给客户对象提供对此单例对象的使用,我们为它提供一个全局访问点,代码如下所示:
public class Singleton {
private static Singleton instance = new Singleton();
//other fields…
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
//other methods…
}
代码注解:
l Singleton类的只有一个构造方法,它是被private修饰的,客户对象无法创建该类实例。
l 我们为此单例实现的全局访问点是public static Singleton getInstance()方法,注意,instance变量是私有的,外界无法访问的。
读者还可以定义instance变量是public的,这样把属性直接暴露给其他对象,就没必要实现public static Singleton getInstance()方法,但是可读性没有方法来的直接,而且把该实例变量的名字直接暴露给客户程序,增加了代码的耦合度,如果改变此变量名称,会引起客户类的改变。
还有一点,如果该实例需要比较复杂的初始化过程时,把这个过程应该写在static{…}代码块中。
l 此实现是线程安全的,当多个线程同时去访问该类的getInstance()方法时,不会初始化多个不同的对象,这是因为,JVM(Java Virtual Machine)在加载此类时,对于static属性的初始化只能由一个线程执行且仅一次[1]。
由于此单例提供了静态的公有方法,那么客户使用单例模式的代码也就非常简单了,如下所示:
Singleton singleton = Singleton.getInstance();
3.3 进阶
3.3.1 延迟创建
如果出于性能等的考虑,我们希望延迟实例化单例对象(Static属性在加载类是就会被初始化),只有在第一次使用该类的实例时才去实例化,我们应该怎么办呢?
这个其实并不难做到,我们把单例的实例化过程移至getInstance()方法,而不在加载类时预先创建。当访问此方法时,首先判断该实例是不是已经被实例化过了,如果已被初始化,则直接返回这个对象的引用;否则,创建这个实例并初始化,最后返回这个对象引用。代码片段如下所示:
public class UnThreadSafeSingelton {
//variables and constructors…
public static UnThreadSafeSingelton getInstance() {
if(instatnce ==null){
instatnce = new UnThreadSafeSingelton();
}
return instatnce;
}
}
我们使用这句if(instatnce ==null) 判断是否实例化完成了。此方法不是线程安全的,接下来我们将会讨论。
3.3.2 线程安全
上节我们创建了可延迟初始化的单例,然而不幸的是,在高并发的环境中,getInstance()方法返回了多个指向不同的该类实例,究竟是什么原因呢?我们针对此方法,给出两个线程并发访问getInstance()方法时的一种情况,如下所示:
t1 t2
1 if(instatnce ==null)
2 if(instatnce ==null)
3 instatnce = new UnThreadSafeSingelton();
4 return instatnce;
5 instatnce = new UnThreadSafeSingelton()
6 return instatnce;
如果这两个线程按照上述步骤执行,不难发现,在时刻1和2,由于还没有创建单例对象,Thread1和Thread2都会进入创建单例实例的代码块分别创建实例。在时刻3,Thread1创建了一个实例对象,但是Thread2此时已无法知道,继续创建一个新的实例对象,于是这两个线程持有的实例并非为同一个。更为糟糕的是,在没有自动内存回收机制的语言平台上运行这样的单例模式,例如使用C++编写此模式,因为我们认为创建了一个单例实例,忽略了其他线程所产生的对象,不会手动去回收它们,引起了内存泄露。
为了解决这个问题,我们给此方法添加synchronized关键字,代码如下:
public class ThreadSafeSingelton {
//variables and constructors…
public static synchronized ThreadSafeSingelton getInstance() {
if(instatnce ==null){
instatnce = new ThreadSafeSingelton();
}
return instatnce;
}
}
这样,再多的线程访问都只会实例化一个单例对象。
3.3.3 Double-Check Locking
上述途径虽然实现了多线程的安全访问,但是在多线程高并发访问的情况下,给此方法加上synchronized关键字会使得性能大不如前。我们仔细分析一下不难发现,使用了synchronized关键字对整个getInstance()方法进行同步是没有必要的:我们只要保证实例化这个对象的那段逻辑被一个线程执行就可以了,而返回引用的那段代码是没有必要同步的。按照这个想法,我们的代码片段大致如下所示:
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton instatnce = null;
//constructors
public static DoubleCheckSingleton getInstance() {
if (instatnce == null) { //check if it is created.
synchronized (DoubleCheckSingleton.class) { //synchronize creation block
if (instatnce == null) //double check if it is created
instatnce = new DoubleCheckSingleton();
}
}
return instatnce;
}
}
代码注解:
l 在getInstance()方法里,我们首先判断此实例是否已经被创建了,如果还没有创建,首先使用synchronized同步实例化代码块。在同步代码块里,我们还需要再次检查是否已经创建了此类的实例,这是因为:如果没有第二次检查,这时有两个线程Thread A和Thread B同时进入该方法,它们都检测到instatnce为null,不管哪一个线程先占据同步锁创建实例对象,都不会阻止另外一个线程继续进入实例化代码块重新创建实例对象,这样,同样会生成两个实例对象。所以,我们在同步的代码块里,进行第二次判断判断该对象是否已被创建。
正是由于使用了两次的检查,我们称之为double-checked locking模式。
l 属性instatnce是被volatile修饰的,因为volatile具有synchronized的可见性特点,也就是说线程能够自动发现volatile变量的最新值。这样,如果instatnce实例化成功,其他线程便能立即发现。
注意:
此程序只有在JAVA 5及以上版本才能正常运行,在以前版本不能保证其正常运行。这是由于Java平台的内存模式容许out-of-order writes引起的,假定有两个线程,Thread 1和Thread 2,它们执行以下步骤:
1. Thread 1发现instatnce没有被实例化,它获得锁并去实例化此对象,JVM容许在没有完全实例化完成时,instance变量就指向此实例,因为这些步骤可以是out-of-order writes的,此时instance==null为false,之前的版本即使用volatile关键字修饰也无效。
2. 在初始化完成之前,Thread 2进入此方法,发现instance已经不为null了,Thread 2便认为该实例初始化完成了,使用这个未完全初始化的实例对象,则很可能引起系统的崩溃。
3.3.4 Initialization on demand holder
要使用线程安全的延迟的单例初始化,我们还有一种方法,称为Initialization on demand holder模式,代码如下所示:
public class LazyLoadedSingleton {
private LazyLoadedSingleton() {
}
private static class LazyHolder { //holds the singleton class
private static final LazyLoadedSingleton singletonInstatnce = new LazyLoadedSingleton();
}
public static LazyLoadedSingleton getInstance() {
return LazyHolder.singletonInstatnce;
}
}
当JVM加载LazyLoadedSingleton类时,由于该类没有static属性,所以加载完成后便即可返回。只有第一次调用getInstance()方法时,JVM才会加载LazyHolder类,由于它包含一个static属性singletonInstatnce,所以会首先初始化这个变量,根据前面的介绍,我们知道此过程并不会出现并发问题(JLS保证),这样即实现了一个既线程安全又支持延迟加载的单例模式。
3.3.5 Singleton的序列化
如果单例类实现了Serializable接口,这时我们得特别注意,因为我们知道在默认情况下,每次反序列化(Desierialization)总会创建一个新的实例对象,这样一个系统会出现多个对象供使用。我们应该怎么办呢?
熟悉Java序列化的读者可能知道,我们需要在readResolve()方法里做文章,此方法在反序列化完成之前被执行,我们在此方法里替换掉反序列化出来的那个新的实例,让其指向内存中的那个单例对象即可,代码实现如下:
import java.io.Serializable;
public class SerialibleSingleton implements Serializable {
private static final long serialVersionUID = -6099617126325157499L;
static SerialibleSingleton singleton = new SerialibleSingleton();
private SerialibleSingleton() {
}
// This method is called immediately after an object of this class is deserialized.
// This method returns the singleton instance.
private Object readResolve() {
return singleton;
}
}
方法readResolve()直接返回singleton单例,这样,我们在内存中始终保持了一个唯一的单例对象。
- 浏览: 1975274 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
x593106671:
写的真不错
对研发团队里技术分享的一些思考 -
Ann-phei:
大神 您好~我是博文视点编辑安娜,可否加我微信或QQ 8030 ...
ElasticSearch的Java Api基本操作入门指南 -
feifeiwudi:
feifeiwudi 写道发现了一个 Elasticsearc ...
ElasticSearch的Java Api基本操作入门指南 -
feifeiwudi:
发现了一个 Elasticsearch 2.3.3 JAVA ...
ElasticSearch的Java Api基本操作入门指南 -
风火轮子:
基于大数据技术推荐系统算法案例实战教程网盘下载:https:/ ...
大数据/数据挖掘/推荐系统/机器学习相关资源
发表评论
-
万字总结Java 9~15新特性
2021-09-11 21:36 553Java9 发布于 2017 年 9 月 21 日 。作为 ... -
架构制图:工具与方法论
2021-07-18 20:08 1075架构制图:工具与方法论 前言 “架构制图”这词乍 ... -
性能优化
2021-07-18 19:56 1000性能问题和Bug不同,后 ... -
【冬察冬见】FFmpeg系列学习笔记
2021-03-16 18:32 753【冬察冬见】FFmpeg系列 ... -
有关创新的一些思考
2021-03-04 16:41 553引子 创造力是人类最变通的工具,创造机会 ... -
浅谈面试官的培养
2021-03-04 16:38 519技术面试是一个工程师成长到一定阶段后必然要承担的一项工作,优 ... -
冬察冬见·全视角再议晋升
2021-03-02 17:48 476冬察冬见·全视角再议晋升 【前言】 又是一年春来 ... -
冬察冬见·晋升-晋升的那些事儿1
2021-03-04 11:34 22892020.08.20 集团技术通道各个子通道通过直播向所有 ... -
物联网MQTT实战
2020-11-04 16:08 7470、MQTT服务器选型 比较流行的开源 MQTT ... -
大小公司都适用的架构选型工具箱(涵盖上百个组件)
2020-10-17 21:20 430本篇内容涵盖14个方面,涉及上百个框架和工具。会有你喜 ... -
elasticsearch使用踩坑
2020-06-17 21:20 491es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊? ... -
【冬察冬见】读书日话高效读书
2020-04-23 10:47 394【冬察冬见】读书日话 ... -
【冬察冬见·荐书】4·23世界读书日 80本书单推荐承包你一年的书单
2020-04-22 18:52 432前言 笔者平均每年保持90-100本书的学习进度,09年 ... -
快速上手 AB Test
2020-01-14 10:05 454什么是 A/B Testing? 关于 A/B 有很多层 ... -
优雅的微服务架构下的鉴权
2020-01-10 17:57 1377从单体应用架构到分布式应用架构再到微服务架构,应用的安全访问 ... -
知识图谱的构建
2020-01-07 20:57 1031随着互联网业务的发展,产生了大量的数据,数据经过分析会推动业 ... -
宜信微服务架构落地及其演进
2020-01-06 09:46 610一、应用服务架构演进及微服务架构介绍 1.1 应用架构的 ... -
MySQL性能优化神技
2019-10-27 14:43 626MySQL 应用优化。包括数据类型,数据表查询/修改,索引和 ... -
REST协议解密(原创)
2019-10-14 10:32 738REST协议解密 REST 全称是什么?RE ... -
大型互联网公司分布式ID方案总结
2019-09-16 19:44 513ID是数据的唯一标识,传统的做法是利用UUID和数据库 ...
相关推荐
【深入浅出单例Singleton模式】 单例模式是一种在软件设计中常见的设计模式,它的核心目标是确保一个类只有一个实例,并提供一个全局访问点。在Java等面向对象编程语言中,单例模式常用于控制资源的共享,如全局...
综上所述,Qt Qml的Singleton模式为全局共享对象提供了方便,使得在Qml环境中管理和访问同一对象变得简单。通过C++和Qml的有效结合,我们可以构建出高效且易于维护的应用程序。了解和熟练运用Singleton模式,对于...
在Java应用中,单例对象能保证在一个...3、有些像交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了,只有使用单例模式,才能保证核心交易服务器独立控制整个流程。 CSDN代码的详细解释。
### 单例模式 Singleton Pattern #### 概述 单例模式是一种常见的设计模式,属于创建型模式之一。这种模式的核心在于确保某个类只有一个实例存在,并且提供一个全局访问点来获取该实例。单例模式在Java开发中尤其...
单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在C++中,实现单例模式有多种方法,我们将会深入探讨这一模式的原理、优缺点以及如何在实际编程中应用。 单例模式的核心在于...
单例模式是软件设计模式中的一种,它的核心思想是确保一个类在整个系统中只有一个实例,并提供一个全局访问点。在Java或类似编程语言中,单例模式常常被用来管理资源,比如数据库连接、线程池或者配置信息,因为这些...
单例设计模式Singleton1是Java设计模式中的一种创建型模式,指某个类采用Singleton模式,则在这个类被创建后,只可能产生一个实例供外部访问,并且提供一个全局的访问点。这个模式的核心知识点可以总结为三点: 1. ...
简单的单例模式举例Singleton 分为恶汉式 懒汉式
Singleton模式包含的角色只有一个,就是Singleton。Singleton拥有一个私有构造函数,确保用户无法通过new直接实例化它。除此之外,该模式中包含一个静态私有成员变量instance与静态公有方法Instance()。Instance()...
单例模式(Singleton Pattern)是一种常用的软件设计模式,属于创建型模式之一。其主要目的是确保一个类只有一个实例,并提供一个全局访问点来访问该实例。这在某些情况下非常有用,比如系统中需要频繁创建后又销毁...
单例设计模式是一种在软件工程中广泛使用的创建型设计模式,其核心思想是确保一个类仅有一个实例,并提供一个全局访问点。这种模式在多种场景下具有显著的优势,同时也存在一定的局限性和潜在的问题。 ### 单例设计...
单例模式是软件设计模式中的一种经典模式,它在许多场景下被广泛使用,尤其是在需要全局唯一实例的情况下。本文将深入探讨单例模式的概念、作用、实现方式以及其在实际编程中的应用。 单例模式的核心思想是确保一个...
C++实现Singleton单例模式 本文档将详细介绍如何使用C++语言实现设计模式中的单例模式。单例模式是一种常用的设计模式,它可以确保一个类只能实例化一次。 单例模式的定义: 单例模式是一种创建型模式,它可以确保...
### Singleton Pattern 单例模式应用详解 #### 一、单例模式概述 单例模式(Singleton Pattern)是一种常用的软件设计模式,在系统中确保某个类只有一个实例,并提供一个全局访问点。这种模式通常用于控制资源的...
单例设计模式是一种在软件设计中广泛使用的设计模式,它属于创建型模式,其核心目标是确保类只有一个实例,并提供一个全局访问点。这个模式在Java中尤其常见,因为Java的垃圾回收机制允许对象长时间驻留在内存中,...
单例设计模式是软件设计模式中的经典模式之一,它的核心思想是确保一个类只有一个实例,并提供全局访问点。这种模式在Java中广泛应用于系统配置、线程池、缓存管理等场景,因为这些场景通常只需要一个共享的全局对象...
"singleton单例模式1" 单例设计模式是软件设计模式中的一种,目的是确保某个类只能有一个实例,并提供一个全局的访问点来访问这个实例。 首先,单例模式的要点是:① 某个类只能有一个实例。构造器私有化。② 它...
单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在C#中,单例模式常用于管理共享资源或控制类的实例化过程,以提高性能、节约系统资源,特别是在整个应用程序生命周期内只需要一...
单例设计模式是一种常用的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在Java编程中,单例模式的应用广泛,例如控制资源的共享、管理配置信息等。本篇将深入解析单例设计模式的四种实现方式,并通过...