- 浏览: 128132 次
- 性别:
- 来自: 北京
文章分类
最新评论
人人都会设计模式:02-单例子模式--Singleton
摘要: 人人都会设计模式系统宗旨是以简洁明了方式让你明白设计模式,本文介绍了单例设计模式,你真的全方位了解过单例模式吗?我们拭目以待
单例模式大纲
版权声明:本文为博主原创文章,未经博主允许不得转载
公众号:TigerChain
TigerChain
欢迎关注
教程简介
1、阅读对象 本篇教程适合新手阅读,老手直接略过
2、教程难度 初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢
3、Demo 地址 https://github.com/githubchen001/DesignPattern 请看 SingleTon 部分
正文
一、什么是单例模式
1、 生活中的单例
一个男人只能有一个媳妇「正常情况」,一个人只能有一张嘴,通常一个公司只有一个 CEO ,一个狼群中只有一个狼王等等
2、程序中的单例
一句话,就是保证一个类仅有一个实例即可「new 一次」,其实好多人都不把单例当作成一个设计模式,只是当作是一个工具类而已,因为它的确很简单,并且当你面视的时候面视官问你设计模式的时候估计都会说:可以说说你了解的设计模式吗「单例除外」。虽然很简单,但是我们还是要掌握和了解它,并且要深层次的了解它
单例模式的定义
单例单例就是单一的实例,单例模式就保证一个类仅有一个实例,并且提供一个可以仿问的全局方法可以访问它
单例模式的应用
网站的计数器
应用配置
多线程池一般也采用单例去设计
数据库配置,数据库连接池
其它等等
单例的特点
不能被外部实例化,只能自己内部实例化自己
单例生成的对象是独一无二的「节省资源」
单例模式的结构
角色 类别 说明
Singleton 单例类 就是一个普通的类
getInstance() 一个静态方法 提供类的实例
单例模式的 UML
单例模式
从上图我们可以了解到编写一个单例的基本步骤「我称之为三步法」
1、成员变量静态化
2、构造方法私有化
3、实例方法静态化
简单的代码结构就是
class SingleTon{
private static SingleTon instance ;
private SingleTon(){}
public static SingleTon getInstance(){
if(null == instance){
instance = new SingleTon();
}
return instance ;
}
}
在实际开发中,我们按照以上三步法就可以创建出一个单例来「直接用方法套用即可」
二、单例模式举例
单例模式举例
比如在一个狼群当中,只有一个狼王,有若干侦察狼、捕猎狼等等,这样就组成了一个狼群,下面看简单的 java 代码「代码只是用来演示单例模式,参考即可」
先看看狼王单例简单的 UML
狼王单例
根据 UML 编码
1、定义一个狼的接口,比如这里是下达任务
public interface IWolf {
void doSomting() ;
}
2、定义一个侦察狼,它是放哨和探路的
/**
* 侦察狼
*/
public class ZhenChaLang implements IWolf {
@Override
public void doSomting() {
// 执行狼王交行的任务
System.out.println(" 去探路");
}
public void fangShao(){
System.out.println(" 去放哨");
}
}
3、定义一个捕猎狼,猎羊
/**
* 捕猎狼
*/
public class BuLieLang implements IWolf {
@Override
public void doSomting() {
System.out.println(" 去猎羊");
}
}
4、主角狼王上场,统一安排规划
/**
* 狼王
*/
public class LangWang implements IWolf {
private static LangWang langWang ;
private LangWang(){
System.out.println("狼王产生了--构造方法被调用");
}
public static LangWang getLangWang(){
if(null == langWang){
langWang = new LangWang() ;
}
System.out.println("狼王对应的地址:"+langWang.toString());
return langWang ;
}
public static void main(String args[]){
LangWang.getLangWang().doSomting();
LangWang.getLangWang().buLie();
}
@Override
public void doSomting() {
// 安排一些工作给下属狼 比如侦查狼
ZhenChaLang zhenChaLang1 = new ZhenChaLang() ;
System.out.print("侦察狼 "+zhenChaLang1.toString());
zhenChaLang1.doSomting();
ZhenChaLang zhenChaLang2 = new ZhenChaLang();
System.out.print("侦察狼 "+zhenChaLang2.toString());
zhenChaLang2.fangShao();
}
public void buLie(){
BuLieLang buLieLang1 = new BuLieLang() ;
System.out.print("捕猎狼 "+buLieLang1.toString());
buLieLang1.doSomting();
BuLieLang buLieLang2 = new BuLieLang() ;
System.out.print("捕猎狼 "+buLieLang2.toString());
buLieLang1.doSomting();
}
}
我们可以看到狼王是一个单例的「一个狼群确实只有一个狼王」,下面我们来验证一下结果
狼王单例分析
我们可以看到,虽然我们调用了两次狼王实例方法确实都是同一个狼王「地址是一样的」,而侦查狼和捕猎狼分别是不同的狼,这就是一个单例的使用,各自体会一下。
上面狼王的例子中我们使用的是非线程安全的懒汉式单例模式,单例模式有好几种实现方式,下面我们来说说这几种实现方式
单例模式的几种实现方式
1、饿汉式
饿汉式单例模式如其名,是一个饿货,类的实例在类加载的时候就初始化出来「把这一过程当作一个汉堡,也就是说必须要把汉堡提前准备好,饿货就知道吃」
特点
1、是线程安全的
2、类不是延时加载「直接是类加载的时候就初始化」
优缺点
优点:没有加锁,执行效率非常高「其实是以空间来换时间」
缺点:在类加载的时候就会初始化,浪费内存「你知道我要不要使用这个实例吗,你就给我初始化,太任性了」
演示代码
public class SingleTon{
// 1、成员变量静态化 饿汉式直接在类加载的时候就初始化实例
private static SingleTon instance = new SingleTon();
// 2、构造方法私有化
private SingleTon(){}
// 3、实例公有方法静态化
public static SingleTon getInstance(){
return instance ;
}
}
2、懒汉式线程不安全
懒汉式单例模式,是在我需要的时候才去初始化实例,也就是说在类加载的时候,静态成员变量是 null 的,只有需要它的时候才去初始化实例,所以懒汉式可以延时加载
特点
1、线程不安全
2、延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化
优缺点
1、优点:延时初始化类,省资源,不想用的时候就不会浪费内存
2、缺点:线程不安全,多线程操作就会有问题
演示代码
public class SingleTon{
// 1、类变量静态化 类加载的时候是空的,所以不开辟内存
private static SingleTon instance = null ;
// 2、构造方法私有化,这没什么好说的
private SingleTon(){}
// 3、实例方法公有并且静态化
public static SingleTon getInstance(){
if(null == instance){
instance = new SingleTon() ;
}
}
return instance ;
}
3、懒汉式线程安全
懒汉式线程安全比懒汉式线程不全多了一个线程安全
特点
1、线程安全
2、延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化化
优缺点
1、优点:延时初始化类,省资源,不想用的时候就不会浪费内存,并且线程安全
2、缺点:虽然线程安全,但是加了锁对性能影响非常大「相当于排队获取资源,没有拿到锁子就干等」
演示代码
public class SingleTon{
private static SingleTon instance ;
private SingleTon(){}
// 在这里加一个同步锁,这样就保证线程安全了
public static synchronized SingleTon getInstance(){
if(null == instalce){
instance = new SingleTon() ;
}
return instance ;
}
}
4、DCL「双重检查锁:double-checked locking」 单例
如其名,双检锁,这种方式单例模式在多线程的情况下能提高性能
特点
1、线程安全
2、延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化化
优缺点
1、优点:延时初始化类,省资源,不想用的时候就不会浪费内存,并且线程安全,双重加锁,多线程仿问性能达到提升「后面详细说 WHY」
2、缺点:虽然线程安全,但是双检锁会遇到指令重排的问题,导致多线程下失效「后面会说」
演示代码
public class DCLSingleTon {
/1、成员变量静态化/
private static DCLSingleTon instance ;
/*2、构造方法私有化/
private DCLSingleTon(){}
/3、实例方法静态化/
public static DCLSingleTon getInstance(){
if(null == instance){ //第一次检查
synchronized (DCLSingleTon.class){ //加锁
if(null == instance){ // 第二次检查
instance = new DCLSingleTon() ;
}
}
}
return instance ;
}
}
双检锁性能提高
那么这种方式,如何保证线程并且有很好的性能呢,首先安全安全不说了看到 synchronized 关键字我们就知道了,这里说一下为什么说性能比 3 中的提高了呢
我们知道线程安全性能主要是出在 synchronized 锁上,我们只要能保证锁最小化调用即可
从上面代码可以看出,只有第一次当 instance 为空的时候,才会去调用 synchronized 中的方法,以后就直接返回 synchronized 实例了,也就说 synchronized 只调用一次,所以在多线程上性能会大大的提升
指令重排引起 DCL 问题
这样做看起来很不错,解决了多线程问题并延时加载,并且同步一次性能有了不错的提升,但是这样做仍然会有问题,这和 Java 的内存模型有关「这种内存模型可以让处理器大大的提高执行效率」
如果再深入的说,就要说 JAVA 的内存模型了「这不在本节范围之内」,大家只要记住,Java 的指令重排会导致多线程问题「单线程不会受影响」,指令排序通俗的说就是代码执行顺序改变了,比如:以下一个简单的例子「下面代码只是为了说明问题,并不是真实情况下的代码」
class A{
private static int a,b = 0 ;
public static void main(String args[]){
a = 1 ;
b = 2 ;
System.out.print("a = "+a+"b = "+b)
}
}
如果按照正常情况下肯定结果是 a=1,b=2。但是如果指令排序多线程情况下就有可能会出现 a=0,b=2 ,也就是 a = 1 和 b =2 调用顺序反过来了「便于理解,实际比这个复杂多了」,这样就大概解释了指令重排,详细可以看看美团点评技术团队的Java内存访问重排序的研究 讲的还是非常好的
DCL 遇到指令重排出现问题分析
上面的问题要从 instance = new SingleTon() 这句初始化开始「由于这是很多条指令,JVM 可能会指令重排,也叫乱序执行」,这个过程分成三个步骤
1、给 instance 分配内存
2、然后调用 SingleTon 的构造方法初始化成员变量
3、把 instance 对象指向分配的内存空间(到这一步,那么 instance 肯定就是非空的)
问题:
如果按照 1 2 3 执行顺序那么也就存在什么问题,可是实际情况是 2 3 执行顺序是不确定的「指令重排序」,这时结果就会成 1 3 2 ,那么问题来了,假如按后者来说,3 刚执行完毕,2 还没有开始之前,突然被另外一个线程2抢占了,此时 instance 已经非空的「但是却没有初始化」,那么线程2会直接返回 instance 去使用,结果就是挂了
DCL 单例多线程问题分析
好了,既然找到了问题,那么解决办法有以下两种
1、不让 2 3 步骤发生指令排序
2、让保证初始化 intance 时只有一个线程来操作「就是单线程操作,单线程不会存在排序问题」
解决方案一:不发生指令排序
使用 volatile 关键字「Java 5 之后 volatile 就可以禁止对指令重新排序 」,就可以指令不发生重排,修改代码
public class DCLSingleTon {
/1、成员变量静态化/
private volatile static DCLSingleTon instance ;
/*2、构造方法私有化/
private DCLSingleTon(){}
/3、实例方法静态化/
public static DCLSingleTon getInstance(){
if(null == instance){ //第一次检查
synchronized (DCLSingleTon.class){ //加锁
if(null == instance){ // 第二次检查
instance = new DCLSingleTon() ;
}
}
}
return instance ;
}
}
当然了,Java 5 之后才能完美的使用 volatile ,那么之前如何解决 DCL 安全问题呢?可以使用 Thread Local ,临时变量等具体可以看关于 DCL 的讲解以及改善 双重锁定被破坏声明 说的非常的好
解决方案二:静态内存部类 其实就是我们要说的第 5 种单例模式
利用 classloder 的机制来保证初始化 instance 时只有一个线程。JVM 在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化
修改代码
public class DCLSingleTon {
private DCLSingleTon(){}
static class SingleTonHolder{
private static final DCLSingleTon instance = new DCLSingleTon() ;
}
public static DCLSingleTon getInstance(){
return SingleTonHolder.instance ;
}
}
5、静态内部类单例模式
静态内部类可以允许指令重排,但是对别的线程是不可见的,那么就想当于单线程指令重排对结果是没有影响的「这是内存模型的特点」,我们来看一下单线程的执行行时序图,我们来看 SingleTon instence = new SingleTon() 这一过程
线程执行时序图
所以静态内存类单例,你就可以理解成一个线程把上述过程做完了,所以别的线程看不见,所以不会出现时间排序的问题
只要保证 2 在 4 的前面,那么 2 3 是否重排,对结果都是没有影响的「在单线程的情况下」
特点
1、线程安全
2、延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化化
优缺点
1、优点:延时初始化类,省资源,不想用的时候就不会浪费内存,并且线程安全,还可以执行其它的静态方法
2、缺点: --
演示代码
public class SingleTon {
private SingleTon(){}
static class SingleTonHolder{
private static final DCLSingleTon instance = new DCLSingleTon() ;
}
public static SingleTon getInstance(){
return SingleTonHolder.instance ;
}
}
静态内部例单例分析
6、枚举类单例
枚举类单例模式是 《Effective Java》 作者极力推荐的单例的方法
特点
特点也就是检举类的特点,我们先看看枚举类的特点吧,多说无用,我们结合 java 代码来分析
// 一周的枚举,这里为了说明问题,只列举到周三
public enum EnumDemo {
MONDAY,
TUESDAY,
WEDNESDAY ;
public void donSomthing(){}
}
以上就是一个简单的枚举 Java 类,我们反编译来看一下它的实现机制是杂样的,在这里我使用 jad 来反编译「当然你也可以使用 javap 来反编译还能看到二进制」,以上 java 代码反编译出来的结果如下:
枚举类反编译
从以上反编译出来的代码图我们可以看出以下几点信息:
1、枚举类类型是 final 的「不可以被继承」
2、构造方法是私有的「也只能私有,不允许被外部实例化,符合单例」
3、类变量是静态的
4、没有延时初始化,随着类的初始化就初始化了「从上面静态代码块中可以看出」
5、由 4 可以知道枚举也是线程安全的
以上就是枚举类的特点,很符合单例模式,并且集成上以上几种单例模式的优点
优缺点
1、优点:除以上特点优点之外,枚举类还有两个优点:写法简单、支持序列化和反序列化操作「以上的单例序列化和反序列化会破坏单例模式」、并且反射也不能调用构造方法
2、缺点: --
演示代码
public enum EnumSingleTon {
INSTACE; // 定义一个枚举原素,代表 EnumSingleTon 一个实例
/**
* 枚举中的构造方法只能写成 private 或是不写「不写默认就是 private」,所以枚举防止外部来实例化对象
*/
EnumSingleTon(){}
/**
* 一些额外的方法
*/
public void doSometing(){
Log.e("枚举类单例","这是枚举单例中的方法") ;
}
}
总结
一般情况下,不建议使用第 2 种和第 3 种懒汉式单例,建议使用第 1 种饿汉式单例,如果项目中明确要使用延时加载那么使用第 5 种静态内存类的单例,如果有序列化反序列化操作可以使用第 6 种单例模式,如果是其它需求可以使用第 4 种 DCL 单例
三、Android 中的单例模式
1、 InputMethodManager 类
InputMethodManager 就一个服务类「输入法类」源码目录 Androidsdk\sources\android-26\android\view\inputmethod,部分代码如下:
@SystemService(Context.INPUT_METHOD_SERVICE)
public final class InputMethodManager {
// 省略若干行代码
...
static InputMethodManager sInstance;
// 省略若干行代码
...
// 以下是构造方法,没有声明权限就是私有的
InputMethodManager(Looper looper) throws ServiceNotFoundException {
this(IInputMethodManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)), looper);
}
// 以下是构造方法,没有声明权限就是私有的
InputMethodManager(IInputMethodManager service, Looper looper) {
mService = service;
mMainLooper = looper;
mH = new H(looper);
mIInputContext = new ControlledInputConnectionWrapper(looper,
mDummyInputConnection, this);
}
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
try {
sInstance = new InputMethodManager(Looper.getMainLooper());
} catch (ServiceNotFoundException e) {
throw new IllegalStateException(e);
}
}
return sInstance;
}
}
// 省略若干行代码
...
}
从上面代码可以看出,InputMethodManager 是一个典型的-- 线程安全的懒汉式单例
2、Editable 类
文件目录:frameworks/base/core/java/android/text/Editable.java 部分代码如下:
private static Editable.Factory sInstance = new Editable.Factory();
/**
* Returns the standard Editable Factory.
*/
public static Editable.Factory getInstance() {
return sInstance;
}
可以看到非常典型的一个饿汉式单例模式
Android 源码中有非常多的单例模式的例子,这里就一一列举了,相信你看完上面的介绍绝对可以写出一个适合自己项目的单例了
到此为止,我们就把单例械说完了,动手试试吧,点赞是一种鼓励,是一种美德
参考资料:
1、美团点评技术团队:Java内存访问重排序的研究
2、双重锁定被破坏声明:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
3、方腾飞 《Java 并发编程的艺术》 第三章 Java 内存模型
版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:yqgroup@service.aliyun.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
用云栖社区APP,舒服~
单例模式大纲
版权声明:本文为博主原创文章,未经博主允许不得转载
公众号:TigerChain
TigerChain
欢迎关注
教程简介
1、阅读对象 本篇教程适合新手阅读,老手直接略过
2、教程难度 初级,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢
3、Demo 地址 https://github.com/githubchen001/DesignPattern 请看 SingleTon 部分
正文
一、什么是单例模式
1、 生活中的单例
一个男人只能有一个媳妇「正常情况」,一个人只能有一张嘴,通常一个公司只有一个 CEO ,一个狼群中只有一个狼王等等
2、程序中的单例
一句话,就是保证一个类仅有一个实例即可「new 一次」,其实好多人都不把单例当作成一个设计模式,只是当作是一个工具类而已,因为它的确很简单,并且当你面视的时候面视官问你设计模式的时候估计都会说:可以说说你了解的设计模式吗「单例除外」。虽然很简单,但是我们还是要掌握和了解它,并且要深层次的了解它
单例模式的定义
单例单例就是单一的实例,单例模式就保证一个类仅有一个实例,并且提供一个可以仿问的全局方法可以访问它
单例模式的应用
网站的计数器
应用配置
多线程池一般也采用单例去设计
数据库配置,数据库连接池
其它等等
单例的特点
不能被外部实例化,只能自己内部实例化自己
单例生成的对象是独一无二的「节省资源」
单例模式的结构
角色 类别 说明
Singleton 单例类 就是一个普通的类
getInstance() 一个静态方法 提供类的实例
单例模式的 UML
单例模式
从上图我们可以了解到编写一个单例的基本步骤「我称之为三步法」
1、成员变量静态化
2、构造方法私有化
3、实例方法静态化
简单的代码结构就是
class SingleTon{
private static SingleTon instance ;
private SingleTon(){}
public static SingleTon getInstance(){
if(null == instance){
instance = new SingleTon();
}
return instance ;
}
}
在实际开发中,我们按照以上三步法就可以创建出一个单例来「直接用方法套用即可」
二、单例模式举例
单例模式举例
比如在一个狼群当中,只有一个狼王,有若干侦察狼、捕猎狼等等,这样就组成了一个狼群,下面看简单的 java 代码「代码只是用来演示单例模式,参考即可」
先看看狼王单例简单的 UML
狼王单例
根据 UML 编码
1、定义一个狼的接口,比如这里是下达任务
public interface IWolf {
void doSomting() ;
}
2、定义一个侦察狼,它是放哨和探路的
/**
* 侦察狼
*/
public class ZhenChaLang implements IWolf {
@Override
public void doSomting() {
// 执行狼王交行的任务
System.out.println(" 去探路");
}
public void fangShao(){
System.out.println(" 去放哨");
}
}
3、定义一个捕猎狼,猎羊
/**
* 捕猎狼
*/
public class BuLieLang implements IWolf {
@Override
public void doSomting() {
System.out.println(" 去猎羊");
}
}
4、主角狼王上场,统一安排规划
/**
* 狼王
*/
public class LangWang implements IWolf {
private static LangWang langWang ;
private LangWang(){
System.out.println("狼王产生了--构造方法被调用");
}
public static LangWang getLangWang(){
if(null == langWang){
langWang = new LangWang() ;
}
System.out.println("狼王对应的地址:"+langWang.toString());
return langWang ;
}
public static void main(String args[]){
LangWang.getLangWang().doSomting();
LangWang.getLangWang().buLie();
}
@Override
public void doSomting() {
// 安排一些工作给下属狼 比如侦查狼
ZhenChaLang zhenChaLang1 = new ZhenChaLang() ;
System.out.print("侦察狼 "+zhenChaLang1.toString());
zhenChaLang1.doSomting();
ZhenChaLang zhenChaLang2 = new ZhenChaLang();
System.out.print("侦察狼 "+zhenChaLang2.toString());
zhenChaLang2.fangShao();
}
public void buLie(){
BuLieLang buLieLang1 = new BuLieLang() ;
System.out.print("捕猎狼 "+buLieLang1.toString());
buLieLang1.doSomting();
BuLieLang buLieLang2 = new BuLieLang() ;
System.out.print("捕猎狼 "+buLieLang2.toString());
buLieLang1.doSomting();
}
}
我们可以看到狼王是一个单例的「一个狼群确实只有一个狼王」,下面我们来验证一下结果
狼王单例分析
我们可以看到,虽然我们调用了两次狼王实例方法确实都是同一个狼王「地址是一样的」,而侦查狼和捕猎狼分别是不同的狼,这就是一个单例的使用,各自体会一下。
上面狼王的例子中我们使用的是非线程安全的懒汉式单例模式,单例模式有好几种实现方式,下面我们来说说这几种实现方式
单例模式的几种实现方式
1、饿汉式
饿汉式单例模式如其名,是一个饿货,类的实例在类加载的时候就初始化出来「把这一过程当作一个汉堡,也就是说必须要把汉堡提前准备好,饿货就知道吃」
特点
1、是线程安全的
2、类不是延时加载「直接是类加载的时候就初始化」
优缺点
优点:没有加锁,执行效率非常高「其实是以空间来换时间」
缺点:在类加载的时候就会初始化,浪费内存「你知道我要不要使用这个实例吗,你就给我初始化,太任性了」
演示代码
public class SingleTon{
// 1、成员变量静态化 饿汉式直接在类加载的时候就初始化实例
private static SingleTon instance = new SingleTon();
// 2、构造方法私有化
private SingleTon(){}
// 3、实例公有方法静态化
public static SingleTon getInstance(){
return instance ;
}
}
2、懒汉式线程不安全
懒汉式单例模式,是在我需要的时候才去初始化实例,也就是说在类加载的时候,静态成员变量是 null 的,只有需要它的时候才去初始化实例,所以懒汉式可以延时加载
特点
1、线程不安全
2、延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化
优缺点
1、优点:延时初始化类,省资源,不想用的时候就不会浪费内存
2、缺点:线程不安全,多线程操作就会有问题
演示代码
public class SingleTon{
// 1、类变量静态化 类加载的时候是空的,所以不开辟内存
private static SingleTon instance = null ;
// 2、构造方法私有化,这没什么好说的
private SingleTon(){}
// 3、实例方法公有并且静态化
public static SingleTon getInstance(){
if(null == instance){
instance = new SingleTon() ;
}
}
return instance ;
}
3、懒汉式线程安全
懒汉式线程安全比懒汉式线程不全多了一个线程安全
特点
1、线程安全
2、延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化化
优缺点
1、优点:延时初始化类,省资源,不想用的时候就不会浪费内存,并且线程安全
2、缺点:虽然线程安全,但是加了锁对性能影响非常大「相当于排队获取资源,没有拿到锁子就干等」
演示代码
public class SingleTon{
private static SingleTon instance ;
private SingleTon(){}
// 在这里加一个同步锁,这样就保证线程安全了
public static synchronized SingleTon getInstance(){
if(null == instalce){
instance = new SingleTon() ;
}
return instance ;
}
}
4、DCL「双重检查锁:double-checked locking」 单例
如其名,双检锁,这种方式单例模式在多线程的情况下能提高性能
特点
1、线程安全
2、延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化化
优缺点
1、优点:延时初始化类,省资源,不想用的时候就不会浪费内存,并且线程安全,双重加锁,多线程仿问性能达到提升「后面详细说 WHY」
2、缺点:虽然线程安全,但是双检锁会遇到指令重排的问题,导致多线程下失效「后面会说」
演示代码
public class DCLSingleTon {
/1、成员变量静态化/
private static DCLSingleTon instance ;
/*2、构造方法私有化/
private DCLSingleTon(){}
/3、实例方法静态化/
public static DCLSingleTon getInstance(){
if(null == instance){ //第一次检查
synchronized (DCLSingleTon.class){ //加锁
if(null == instance){ // 第二次检查
instance = new DCLSingleTon() ;
}
}
}
return instance ;
}
}
双检锁性能提高
那么这种方式,如何保证线程并且有很好的性能呢,首先安全安全不说了看到 synchronized 关键字我们就知道了,这里说一下为什么说性能比 3 中的提高了呢
我们知道线程安全性能主要是出在 synchronized 锁上,我们只要能保证锁最小化调用即可
从上面代码可以看出,只有第一次当 instance 为空的时候,才会去调用 synchronized 中的方法,以后就直接返回 synchronized 实例了,也就说 synchronized 只调用一次,所以在多线程上性能会大大的提升
指令重排引起 DCL 问题
这样做看起来很不错,解决了多线程问题并延时加载,并且同步一次性能有了不错的提升,但是这样做仍然会有问题,这和 Java 的内存模型有关「这种内存模型可以让处理器大大的提高执行效率」
如果再深入的说,就要说 JAVA 的内存模型了「这不在本节范围之内」,大家只要记住,Java 的指令重排会导致多线程问题「单线程不会受影响」,指令排序通俗的说就是代码执行顺序改变了,比如:以下一个简单的例子「下面代码只是为了说明问题,并不是真实情况下的代码」
class A{
private static int a,b = 0 ;
public static void main(String args[]){
a = 1 ;
b = 2 ;
System.out.print("a = "+a+"b = "+b)
}
}
如果按照正常情况下肯定结果是 a=1,b=2。但是如果指令排序多线程情况下就有可能会出现 a=0,b=2 ,也就是 a = 1 和 b =2 调用顺序反过来了「便于理解,实际比这个复杂多了」,这样就大概解释了指令重排,详细可以看看美团点评技术团队的Java内存访问重排序的研究 讲的还是非常好的
DCL 遇到指令重排出现问题分析
上面的问题要从 instance = new SingleTon() 这句初始化开始「由于这是很多条指令,JVM 可能会指令重排,也叫乱序执行」,这个过程分成三个步骤
1、给 instance 分配内存
2、然后调用 SingleTon 的构造方法初始化成员变量
3、把 instance 对象指向分配的内存空间(到这一步,那么 instance 肯定就是非空的)
问题:
如果按照 1 2 3 执行顺序那么也就存在什么问题,可是实际情况是 2 3 执行顺序是不确定的「指令重排序」,这时结果就会成 1 3 2 ,那么问题来了,假如按后者来说,3 刚执行完毕,2 还没有开始之前,突然被另外一个线程2抢占了,此时 instance 已经非空的「但是却没有初始化」,那么线程2会直接返回 instance 去使用,结果就是挂了
DCL 单例多线程问题分析
好了,既然找到了问题,那么解决办法有以下两种
1、不让 2 3 步骤发生指令排序
2、让保证初始化 intance 时只有一个线程来操作「就是单线程操作,单线程不会存在排序问题」
解决方案一:不发生指令排序
使用 volatile 关键字「Java 5 之后 volatile 就可以禁止对指令重新排序 」,就可以指令不发生重排,修改代码
public class DCLSingleTon {
/1、成员变量静态化/
private volatile static DCLSingleTon instance ;
/*2、构造方法私有化/
private DCLSingleTon(){}
/3、实例方法静态化/
public static DCLSingleTon getInstance(){
if(null == instance){ //第一次检查
synchronized (DCLSingleTon.class){ //加锁
if(null == instance){ // 第二次检查
instance = new DCLSingleTon() ;
}
}
}
return instance ;
}
}
当然了,Java 5 之后才能完美的使用 volatile ,那么之前如何解决 DCL 安全问题呢?可以使用 Thread Local ,临时变量等具体可以看关于 DCL 的讲解以及改善 双重锁定被破坏声明 说的非常的好
解决方案二:静态内存部类 其实就是我们要说的第 5 种单例模式
利用 classloder 的机制来保证初始化 instance 时只有一个线程。JVM 在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化
修改代码
public class DCLSingleTon {
private DCLSingleTon(){}
static class SingleTonHolder{
private static final DCLSingleTon instance = new DCLSingleTon() ;
}
public static DCLSingleTon getInstance(){
return SingleTonHolder.instance ;
}
}
5、静态内部类单例模式
静态内部类可以允许指令重排,但是对别的线程是不可见的,那么就想当于单线程指令重排对结果是没有影响的「这是内存模型的特点」,我们来看一下单线程的执行行时序图,我们来看 SingleTon instence = new SingleTon() 这一过程
线程执行时序图
所以静态内存类单例,你就可以理解成一个线程把上述过程做完了,所以别的线程看不见,所以不会出现时间排序的问题
只要保证 2 在 4 的前面,那么 2 3 是否重排,对结果都是没有影响的「在单线程的情况下」
特点
1、线程安全
2、延时初始化类,在我需要的时候「也就调用 getInstance」的时候才去初始化化
优缺点
1、优点:延时初始化类,省资源,不想用的时候就不会浪费内存,并且线程安全,还可以执行其它的静态方法
2、缺点: --
演示代码
public class SingleTon {
private SingleTon(){}
static class SingleTonHolder{
private static final DCLSingleTon instance = new DCLSingleTon() ;
}
public static SingleTon getInstance(){
return SingleTonHolder.instance ;
}
}
静态内部例单例分析
6、枚举类单例
枚举类单例模式是 《Effective Java》 作者极力推荐的单例的方法
特点
特点也就是检举类的特点,我们先看看枚举类的特点吧,多说无用,我们结合 java 代码来分析
// 一周的枚举,这里为了说明问题,只列举到周三
public enum EnumDemo {
MONDAY,
TUESDAY,
WEDNESDAY ;
public void donSomthing(){}
}
以上就是一个简单的枚举 Java 类,我们反编译来看一下它的实现机制是杂样的,在这里我使用 jad 来反编译「当然你也可以使用 javap 来反编译还能看到二进制」,以上 java 代码反编译出来的结果如下:
枚举类反编译
从以上反编译出来的代码图我们可以看出以下几点信息:
1、枚举类类型是 final 的「不可以被继承」
2、构造方法是私有的「也只能私有,不允许被外部实例化,符合单例」
3、类变量是静态的
4、没有延时初始化,随着类的初始化就初始化了「从上面静态代码块中可以看出」
5、由 4 可以知道枚举也是线程安全的
以上就是枚举类的特点,很符合单例模式,并且集成上以上几种单例模式的优点
优缺点
1、优点:除以上特点优点之外,枚举类还有两个优点:写法简单、支持序列化和反序列化操作「以上的单例序列化和反序列化会破坏单例模式」、并且反射也不能调用构造方法
2、缺点: --
演示代码
public enum EnumSingleTon {
INSTACE; // 定义一个枚举原素,代表 EnumSingleTon 一个实例
/**
* 枚举中的构造方法只能写成 private 或是不写「不写默认就是 private」,所以枚举防止外部来实例化对象
*/
EnumSingleTon(){}
/**
* 一些额外的方法
*/
public void doSometing(){
Log.e("枚举类单例","这是枚举单例中的方法") ;
}
}
总结
一般情况下,不建议使用第 2 种和第 3 种懒汉式单例,建议使用第 1 种饿汉式单例,如果项目中明确要使用延时加载那么使用第 5 种静态内存类的单例,如果有序列化反序列化操作可以使用第 6 种单例模式,如果是其它需求可以使用第 4 种 DCL 单例
三、Android 中的单例模式
1、 InputMethodManager 类
InputMethodManager 就一个服务类「输入法类」源码目录 Androidsdk\sources\android-26\android\view\inputmethod,部分代码如下:
@SystemService(Context.INPUT_METHOD_SERVICE)
public final class InputMethodManager {
// 省略若干行代码
...
static InputMethodManager sInstance;
// 省略若干行代码
...
// 以下是构造方法,没有声明权限就是私有的
InputMethodManager(Looper looper) throws ServiceNotFoundException {
this(IInputMethodManager.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)), looper);
}
// 以下是构造方法,没有声明权限就是私有的
InputMethodManager(IInputMethodManager service, Looper looper) {
mService = service;
mMainLooper = looper;
mH = new H(looper);
mIInputContext = new ControlledInputConnectionWrapper(looper,
mDummyInputConnection, this);
}
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
try {
sInstance = new InputMethodManager(Looper.getMainLooper());
} catch (ServiceNotFoundException e) {
throw new IllegalStateException(e);
}
}
return sInstance;
}
}
// 省略若干行代码
...
}
从上面代码可以看出,InputMethodManager 是一个典型的-- 线程安全的懒汉式单例
2、Editable 类
文件目录:frameworks/base/core/java/android/text/Editable.java 部分代码如下:
private static Editable.Factory sInstance = new Editable.Factory();
/**
* Returns the standard Editable Factory.
*/
public static Editable.Factory getInstance() {
return sInstance;
}
可以看到非常典型的一个饿汉式单例模式
Android 源码中有非常多的单例模式的例子,这里就一一列举了,相信你看完上面的介绍绝对可以写出一个适合自己项目的单例了
到此为止,我们就把单例械说完了,动手试试吧,点赞是一种鼓励,是一种美德
参考资料:
1、美团点评技术团队:Java内存访问重排序的研究
2、双重锁定被破坏声明:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
3、方腾飞 《Java 并发编程的艺术》 第三章 Java 内存模型
版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:yqgroup@service.aliyun.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
用云栖社区APP,舒服~
相关推荐
这个吉林大学的SDP02-06例子代码很可能是为了教学目的,帮助学生理解和应用设计模式。下面,我们将深入探讨设计模式的核心概念以及可能包含在这个压缩包中的具体模式。 设计模式不是具体的代码或库,而是一套通用的...
吉林大学的“软件设计模式SDP02-05例子代码”提供了对几种核心设计模式的实际应用示例,这有助于学习者深入理解这些模式的用法和目的。 设计模式分为三大类:创建型、结构型和行为型。在这个资料中,我们可能找到...
李建忠面向对象设计模式视频精讲:Singleton 单件(创建型模式)
人人都懂设计模式 设计模式是软件开发中的一种解决方案,它提供了一种通用的设计思想和方法论,可以帮助开发者更好地设计和实现软件系统。设计模式可以分为三大类:创建型模式、结构型模式和行为型模式。 在本书中...
本书结合设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好、表达清楚的软件设计模式,这些模式在实用环境下特别...
"设计模式复习题.doc" 以下是根据给定文件生成的相关知识点: 1. 设计模式的种类: - 工厂方法模式 - 抽象工厂模式 - 单件模式 - 组合模式 - 外观模式 - 观察者模式 - 模板方法模式 - 迭代器模式 - 代理...
设计模式是软件工程中的一种最佳实践,用于解决在软件开发过程中常见的问题。这些模式是由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides四位大师,通常被称为GoF(Gang of Four),在他们的经典著作...
吉林大学提供的这个"SDP03-02例子代码"压缩包文件,旨在帮助学生和开发者深入理解设计模式的概念及其应用。设计模式是经验丰富的软件工程师们在面对特定编程挑战时总结出来的通用解决方案模板,它们可以提高代码的...
包括抽象工厂模式(Abstract Factory)、建造者模式(Builder)、工厂方法模式(Factory Method)、原型模式(Prototype)和单例模式(Singleton)等。 - **结构型模式**:关注于类或对象的组合方式,使它们能形成更...
Singleton模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个唯一的实例。这种模式在需要控制资源的唯一性或者全局访问点时非常有用,比如数据库连接、线程池或者缓存管理等。 ...
### 使用设计模式的实际例子 以**组合模式**为例,假设我们需要设计一个系统来管理各种类型的电脑产品。我们可以定义一个抽象的`Computer`类,然后根据类型的不同派生出`DesktopComputer`和`NotebookComputer`类。...
《吉林大学软件设计模式SDP03-01例子代码》是针对软件开发中的一个重要主题——设计模式的实例代码集合。设计模式是软件工程中经过长期实践和验证的解决方案模板,它为解决常见问题提供了标准的方法。在这个压缩包中...
设计模式-Singleton与Factory
单例模式是软件设计模式中的一种经典模式,它在许多场景下被广泛使用,尤其是在需要全局唯一实例的情况下。本文将深入探讨单例模式的概念、作用、实现方式以及其在实际编程中的应用。 单例模式的核心思想是确保一个...
这里我们将深入探讨三种常见的Java设计模式:单例(Singleton)、工厂方法(Factory Method)和抽象工厂(Abstract Factory)。 **单例模式(Singleton)** 单例模式确保一个类只有一个实例,并提供一个全局访问点...
C#视频-面向对象设计模式纵横谈(2):Singleton 单件(创建型模式)
例如,代理模式(Proxy Pattern)、单例模式(Singleton Pattern)、工厂方法模式(Factory Method Pattern)、抽象工厂模式(Abstract Factory Pattern)、适配器模式(Adapter Pattern)、模板方法模式(Template ...
《C++设计模式--基于Qt4开源跨平台开发框架》一书主要探讨了如何在C++编程中利用设计模式,并结合Qt4框架进行高效的跨平台应用开发。设计模式是软件工程中的重要概念,它们是经过时间和实践验证的解决特定问题的模板...
单件模式(Singleton pattern)是设计模式中的一种结构型模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于系统中需要频繁创建和销毁的对象,如日志服务、线程池或者数据库连接等...
这份名为“设计模式PPT——25种设计模式详解”的资料,显然是一个深入探讨设计模式的教程,它通过PDF格式对25种主要的设计模式进行了详尽的阐述。 首先,我们要理解设计模式的基本概念。设计模式不是代码,而是一种...