Java设计模式之单例模式 - Singleton
用来创建独一无二的,是能有一个实例的对象的入场券。告诉你一个好消息,单例模式的类图可以说是所有模式的类图中最简单的,事实上,它的类图上只有一个类!但是,可不要兴奋过头,尽管从类设计的视角来说很简单,但是实现上还是会遇到相当多的波折。所以,系好安全带,出发了!
介绍
定义
单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。
常用情景
有些对象其实我们只需要一个,比如:windows的任务管理器,项目中的读取配置文件的对象,数据库连接池,spring中的bean默认也是单例,线程池(threadpool),缓存(cache),对话框,处理偏好设置和注册表(registry)的对象,日志对象,充当打印机,显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常,资源使用过量,或者是不一致的结果。
要点
优点
- 单例模式存在一个全局访问点,所以优化共享资源;
- 只生成一个实例,减少了开销,对于一些需要频繁创建和销毁的对象,单例模式可以提高系统性能。
缺点
- 由于单例模式中没有抽象层,因此扩展困难;
- 职责过重,在一定程度上违背了“但一职责原则”。
类图
在java中实现单例模式需要注意以下三点:
- 私有的构造器;
- 一个静态方法;
- 一个静态变量。
单例模式的实现
饿汉式
代码实现
/** 通过饿汉式创建单例模式
* 当前类只能创建一个对象
* 天然线程安全(类只要被加载,就会被加载到全局变量中。所以饿汉式就是及时加载,不能实现懒加载)
*/
public class SingleHungry {
//提供一个静态的全局变量作为访问该实例的入口
private static SingleHungry instance = new SingleHungry();
/**
* 构造器私有,不让外部通过new创建实例
*/
private SingleHungry() {}
/**
* 对外提供静态的方法,用来获取该类的实例
*/
public static SingleHungry getInstance() {
return instance;
}
}
要点
优点:线程安全;
缺点:不能懒加载。
懒汉式
代码实现
/**
* 懒汉式,完成单例模式:
* 静态的全局变量,初始化放在了静态方法中,延迟产生了实例
* 延迟加载
* 线程不安全
*/
public class Singlelazy {
//提供一个静态的全局变量作为访问该实例的入口,但不立即加载
private static Singlelazy instance = null;
/**
* 构造器私有
*/
private Singlelazy() {
System.out.println("构造器被调用了");
}
/**
* 提供方法获取该类的实例
* @return
*/
public static Singlelazy getInstance() {
//先查看是否存在对象,不存在则创建
if(instance == null) {
instance = new Singlelazy();
}
return instance;
}
}
以上代码存在的问题如图:

这段代码是线程不安全的,当有多个线程同时访问该代码时,会出现创建多个实例的情况。
为了方便理解,我以Head First设计模式中的图来介绍:

以上模拟了多线程问题的运行步骤,只是对象的名称不一样而已,相信对你来说是没问题的。
解决思路1:
加锁。锁方法

问题:可以解决问题,但锁住整个方法的粒度太大了,效率较低。不推荐使用
解决思路2:
加锁。锁代码块,先判断后锁

问题:不能解决问题,以上方式锁的粒度变小了,但是并不能产生一个实例。原因:多个线程判断之后全局变量都是null,进入后都开始等锁。线程1出去,线程2进来继续实例化,所以得到的对象是多个。
解决思路3:
加锁。锁代码块,先锁后判断。

以上方式解决了问题,当有多个线程同时访问getInstance()。保证了多个线程是以流式,次序性的进入当前方法来获取该类的实例。那么效率一样很低。而且多个线程同时等待。
上面的解决方法似乎都不怎么好,那么有没有一种更好的方法来解决懒汉式的多线程安全问题呢?请继续探索... ...
双重检测(双重校验锁)
代码实现
public class Singlelazy {
/*
* 双重检测(双重校验锁)
* 延迟加载
* 线程安全
*/
private static volatile Singlelazy instance;
/*
* 构造器私有
*/
private Singlelazy() {
System.out.println("构造器被调用了");
}
public static Singlelazy getInstance() {
if(instance == null) {
synchronized (Singlelazy.class) {
if(instance == null) {
instance = new Singlelazy();
}
}
}
return instance;
}
}
要点
volatile关键字确保:当instance变量被初始化成Singleton实例时,多个线程正确地处理instance变量。
值得注意的是:很不幸地,在jdk1.5之前的版本,许多JVM对于volatile关键字的实现会导致dcl(double check locking)失效。如果你不能使用jdk1.4之后的版本,就请不要利用此技巧实现单例模式。
双重检测很好的解决了懒汉式多线程安全问题,可以和之前的几种解决思路对比一下,思考一下优点在哪里!
解决思路:
我们知道为了解决这个线程安全问题,必须加锁,并且必须先锁后判断,思路3已经解决了问题,为了进一步优化代码执行效率,我们再来改进一下代码:

我们在锁的外面再加一层对全局变量的判断,这么做的效果是什么呢?当有多个线程来访问时,例如:
- 线程1,线程2同时进入该方法;
- 经过最外面的判断后,发现还没有创建实例,这时就会依次执行锁里的逻辑,并创建了实例;
- 假如这时线程3进入该方法,执行最外面的判断,发现已经创建了实例,这时候直接返回就可以了,并不需要等锁。
静态内部类(饿汉式的变种)
代码实现
/*
* 静态内部类创建单例(利用类加载机制,保证线程安全问题)
* 线程安全
* 懒加载
*/
public class SingleInner {
private SingleInner() {}
private static class SingleInnerHolder{
private static SingleInner instance = new SingleInner();
}
public static SingleInner getInsstance() {
return SingleInnerHolder.instance;
}
}
要点
和饿汉式一样采用的是classLoader机制,保证了线程安全问题,但不同的是,静态内部类同样满足懒加载(当调用getInsstance()方法时,实例才会被创建)。
枚举实现
代码实现
public enum SingleEnum {
//定义示例化的单例对象
INSTANCE;
/*
* 对象执行的功能
*/
public void getInstance() {
}
}
要点
枚举类的单例模式,不存在出现序列化,反射构建对象的漏洞(前面几种单例实现方式都存在此问题)。不能懒加载。默认情况枚举实例的创建都是线程安全,但是实例对象实现的方法,需要自己保证线程安全问题。
反射,序列化与单例的关系
反射
我们以饿汉式为例,如下图:

通过反射获取的s3和通过饿汉式获取的s1,s2,并不是同一个实例,那么有什么办法可以解决反射对单例的影响呢!请看下面:
解决方法:
我们通过改变饿汉式构造方法的方式,来解决这个问题:

调用时抛出异常:

序列化
依旧以饿汉式为例,如下图:

通过序列化,反序列化获取的s3和通过饿汉式获取的s1,s2,并不是同一个实例,那么有什么办法可以解决序列化,反序列化对单例的影响呢!请看下面:

通过在饿汉式中加入此方法,就可以解决这个问题:

类加载器
每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次,如果这样的事发生在单例上,就会产生多个单件并存的怪异现象,所以,如果程序中有多个类加载器又同时使用了单例模式,那么,请自行指定类加载器,并指定同一个类加载器。
创建的构建单例模式的方式比较
- 饿汉式 效率较高 不能懒加载 线程安全 调用率高
- 懒汉式 效率较低 懒加载 线程不安全
- 双重检测(双重校验锁)懒汉式的一个变种 效率较高 懒加载 线程安全
- 静态内部类 饿汉式的变种 效率较高 懒加载 线程安全
- 枚举 效率较高 线程安全 不能懒加载
用法总结
- 懒汉式效率最低;
- 占用资源较少 不需要懒加载 枚举优先 饿汉式;
- 占用资源较多 需要懒加载 静态内部类优先 懒汉式(优先使用DCL)。
结语
设计模式源于生活
Java设计模式之单例模式 - Singleton的更多相关文章
- java设计模式之 单例模式 Singleton
static 的应用 单例模式 Singleton 单例:保证一个类在系统中最多只创建一个实例. 好处:由于过多创建对象实例,会产生过多的系统垃圾,需要GC频繁回收,由于GC会占用较大的系统资源,所有 ...
- Java 设计模式(三)-单例模式(Singleton Pattern)
1 概念定义 1.1 定义 确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 1.2 类型 创建类模式 1.3 难点 1)多个虚拟机 当系统中的单例类被拷贝运行在多 ...
- 设计模式之单例模式——Singleton
设计模式之单例模式--Singleton 设计意图: 保证类仅有一个实例,并且可以供应用程序全局使用.为了保证这一点,就需要这个类自己创建自己的对象,并且对外有 ...
- java 设计模式之单例模式
-------Success is getting what you want, happiness is wanting what you get. java设计模式之单例模式(Singleton) ...
- 设计模式(4) -- 单例模式(Singleton)
设计模式(4) -- 单例模式(Singleton) 试想一个读取配置文件的需求,创建完读取类后通过New一个类的实例来读取配置文件的内容,在系统运行期间,系统中会存在很多个该类的实例对象,也就是说 ...
- 折腾Java设计模式之单例模式
博文原址:折腾Java设计模式之单例模式 单例模式 Ensure a class has only one instance, and provide a global point of access ...
- Java设计模式之单例模式(七种写法)
Java设计模式之单例模式(七种写法) 第一种,懒汉式,lazy初始化,线程不安全,多线程中无法工作: public class Singleton { private static Singleto ...
- Java 设计模式之单例模式(一)
原文地址:Java 设计模式之单例模式(一) 博客地址:http://www.extlight.com 一.背景 没有太多原由,纯粹是记录和总结自己从业以来经历和学习的点点滴滴. 本篇内容为 Java ...
- java设计模式之单例模式你真的会了吗?(懒汉式篇)
java设计模式之单例模式你真的会了吗?(懒汉式篇) 一.什么是单例模式? 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供 ...
随机推荐
- js的浅克隆和深克隆
谈一谈个人对js浅克隆和深克隆的区别. 之前也看到很多博客在写,当然也有写的非常好的,但是个人觉得既然要分享就不要写的太深奥,尽量以简单易懂为主. 浅克隆其实就是 对象A = 对象B:如果改变了对象B ...
- Win32编程点滴3 - 简单ActiveX控件的使用
虽然这里一片的.net气氛,到处充斥着像MVC.WPF.WorkFlow.LINQ等各种niubility的术语.但我们使用的Windows还是由COM技术主宰着:我们在选择日常使用的软件时,也会避免 ...
- 1.1-1.5 flume架构概述及安装使用
一.flume架构概述 1.flume简介 Flume是一种分布式,可靠且可用的服务,用于有效地收集,聚合和移动大量日志数据.它具有基于流数据流的简单灵活的架构.它具有可靠的可靠性机制和许多故障转移和 ...
- C# sbyte[]转byte[]
http://stackoverflow.com/questions/2995639/sbyte-vs-byte-using-methodssbyte[] orig = ... byte[] arr ...
- CodeForces 586D【BFS】
题意: s是这个人开始位置:连续相同大写字母是 Each of the k trains,相应的火车具有相应的字母: '.' 代表空: 有个人在最左列,上面有连续字母代表的火车,火车从左边出去的话,会 ...
- 换装demo时美术遇到的问题详解
1.武器替换:MAX的东西进Unity,根骨骼X轴会有270度的旋转. 解决方法:由程序强制武器进入Unity后的旋转角度. 2.蒙皮问题:face和hair等脖子以上部位蒙皮的时候,导入Unity后 ...
- BAT或赌在当下或押在未来,谁是王者?
转自:http://www.tmtpost.com/97132.html 百度阿里和腾讯三家本来的核心业务并不冲突,各守一方阵地,但随着各自的收购注资加上业务的延展而慢慢有了交际,阿里和腾讯在移动支付 ...
- atcoder#073D(枚舉)
題目鏈接: http://arc073.contest.atcoder.jp/tasks/arc073_b 題意: 給出n, m兩個數, n是物品數目, m是背包容量, 接下來n行輸入, wi, vi ...
- 洛谷P2480 [SDOI2010]古代猪文(卢卡斯定理+中国剩余定理)
传送门 好吧我数学差的好像不是一点半点…… 题目求的是$G^{\sum_{d|n}C^d_n}mod\ 999911659$ 我们可以利用费马小定理$a^{k}\equiv a^{k\ mod\ (p ...
- CentOS 利用 yum 安装卸载软件常用命令
一.yum安装和卸载软件 有个前提是yum安装的软件包都是rpm格式的. 安装的命令是,yum install ~,yum会查询数据库,有无这一软件包,如果有,则检查其依赖冲突关系,如果没有依赖冲突, ...