设计模式学习之路 - 单例模式 - only you

原创 2016年08月28日 20:03:38

今天我们了解一下单例模式,这个模式似乎是笔试最多的模式之一了(面试几乎必问, 似乎面试官特别感兴趣)

单例,从字面意思看, 就是单独的实例, 表示这个实例是唯一的。

那么很多人就会问了,为什么需要这种只有一个实例的类。

其实,在开发中, 很多对象我们都只需要一个,比如:线程池、缓存等等.

实际上这些对象也有且只能有一个,多个实例的话,反而会有问题,或为程序异常,或为资源不足。


我们先贴一下单例模式的定义: 确保一个类只有一个实例, 并提供一个全局访问点


单例模式的代码其实很简单,按照正常的思路会这样做。

package com.chris.single;

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

定义一个静态变量, 当变量存在就返回,不存在就直接创建一个,这样就只有一个实例了。


如果一般的情况,这段代码好像没有什么问题,能确保只有一个实例。

但是, 这时候又要扯到另一个面试必问的问题了, 多线程!!

当有多个线程去执行获取实例的方法时, 可能会同时会判断到uniqueInstance == null 这个条件,这时候就会同时创建实例,这个类就有两个实例了。

有两个实例就不是only you了, 就不是唯一了,所以我们需要对多线程的情况稍微处理一下。


一到同步的问题,大多数开发人员脑海中立刻回浮现出一个单词 : synchronized!!

的确, 只要用synchronized对方法进行修饰, 多线程的灾难几乎就可以轻易的解决。

package com.chris.single;

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

synchronized关键字,可以保证一个线程进入这个方法之后,对其加一个线程锁, 其他线程在这个线程离开这个方法之前,

都必须等待,不会有两个线程同时进入这个方法。


这个方法好像是操作起来最快的方法了, 加一个关键字进行修饰就可以解决。

但是,在很多书上都会提及, synchronized关键字, 是一个十分重量级的东西,会很大的影响性能,无论是修饰方法或者同步一个代码块。

所以,如果你的应用程序能够接受这个造成的额外的性能负担, 那么上述代码就是你想要的单例模式, 这个实现方式叫懒加载(lazy-init)!


或者, 你的应用程序在创建和运行时的负担不是很重, 这时候我们也可以使用更简便的方式, 急加载(eagerly-init)!

package com.chris.single;

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

这种方式会在程序编译后立即加载这个类并创建唯一的实例,而不是在运行时创建, 这样就可以保证只有一个实例。

但是这个类一直都没有被用到的话,这就会浪费空间了,这也是这个方式的一个缺点。


然后如果想综合考虑的话, 这里就有另一种解决办法, 双重校验锁(double-cheked locking)!

package com.chris.single;

public class Singleton {
	private volatile static Singleton uniqueInstance;
	
	private Singleton(){
		
	}
	
	public static Singleton getInstance(){
		if(uniqueInstance == null){
			synchronized (Singleton.class) {
				if(uniqueInstance == null){
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}
为什么叫双重, 因为他有两个关于同步的关键字, 一个是synchronized, 另一个是volatile.

这个方式会首先检查实例是否被创建,如果未创建,这时候才进行同步,所以,同步只会有一次,相比懒加载方式,这个会大大的减少时间耗费,提高性能。

这个方式好像看起来十分的完美了,但是任何介绍这个双重校验锁方式的读物,都会强调一个事情。

在1.4以及更早的版本中,许多JVM对volatile这个关键字的实现会导致双重校验的失效,所以如果不能用java5以后的版本,就不要用这个方式了(用4以前的几乎没有了吧!)


然后我们聊聊volatile,为什么要用这个关键字呢, 因为虽然synchronized能保证进入创建实例的代码是同步的,但是new Singleton() 这个操作缺却不是原子的。

在创建实例的时候,我们的JVM一般会做三件事:

1. 给uniqueInstance分配内存;

2.调用类的构造方法初始化成员变量;

3.将uniqueInstance对象指向分配的内存空间。

当操作3完成之后,其实这个对象就已经不为null了。

而JVM中的JIT(即时编译器)存在指令重排序的优化,所以,步骤2和步骤3的顺序是不能保证的。

最终可能为1-2-3, 也可能为1-3-2,而如果是后者的话,先完成了内存的分配,而这时候另一个线程进来了,判断实例不为null, 直接返回,

但是这个实例却没有经过第2步初始化, 程序就理所当然的报错了。

volatile这个关键字能保证修饰的参数对所有的线程都是可见的, 并且禁止指令重排序优化

对所有的线程可见,是表示线程中不会存在该实例的副本,每次需要拿的话都会去主内存中拿。

而禁止指令重排序优化,就如上述所说,能保证他的操作的执行顺序在JIT进行优化时不会被打乱。


在开发中,自己写单例好像也比较少,一般都是用的第三方的,所以只能了解一下大概的概念。

如果在文中有什么错误的地方,还望指正,和大家共勉。。。






版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

我的设计模式学习之路一——单例模式

一、介绍 定义: Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。” 作用: 1、当我们在两个类中需要访问同一个类中的内容时,这时...

Java实践之路(设计模式)——单例模式(Singleton)

1、概念:单例模式就是保证一个类只存在一个实例为之服务 2、作用:节省内存资源,利于垃圾回收 3、实现:单例模式一般分为两种实现方式        方法一:使用类内部已经new好的实例,代码如下...

设计模式学习之路 - 序言 - 记录自己的成长历程

做开发也有一段时间了, 从最开始的加班加点,为了不拖了其他人的进度甚至自己默默通宵认真学习的小鲜肉, 到现在的懒惰无力不思进取, 当一天和尚撞一天钟的小油条, 思绪万千, 觉得该整理一下思绪了, 互联...

我的设计模式学习之路4(责任链)

责任链(Chain Of Resonsibility)    角色:    抽象处理者角色:定义出一个处理请求的接口,接口可以定义出一个或多个方法,以设定具体处理者所做的动作。    具...

java学习之路之接口(3)--工厂设计模式

java学习之路之接口(3) -----工厂设计模式 首先观察一段程序代码: interface Fruit{ public void eat(); } class Apple implemen...

设计模式学习之路——Builder

作用:  将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示。  UML结构图:略 在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子...

设计模式学习之路——factory和abstract factory

factory 模式 作用:定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。 UML图:网上很多。此处略去 个人理解: fac...

Java重修之路(七)面向对象之静态,主函数,Doc文档,静态代码块,对象初始化过程,单例设计模式

静态(Static) 是一个修饰符,用于修饰成员(包括成员变量和成员函数) 被Static修饰的变量不在堆内存中,被提取到共享区域中,节省空间。被对象所共享。 public class Pe...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)