java设计模式之单例模式写法,懒汉,饿汉,双检锁
2014-10-14 10:26
459 查看
关键字: singleton 单例 写法 双锁 线程安全
饿汉式单例类
public class Singleton
{
private Singleton(){
}
private static Singleton instance = new Singleton();
private static Singleton getInstance(){
return instance;
}
}
饿汉式提前实例化,没有懒汉式中多线程问题,但不管我们是不是调用getInstance()都会存在一个实例在内存中
内部类式单例类
public class Singleton
{
private Singleton(){
}
private class SingletonHoledr(){
private static Singleton instance = new Singleton();
}
private static Singleton getInstance(){
return SingletonHoledr.instance;
}
}
内部类式中,实现了延迟加载,只有我们调用了getInstance(),才会创建唯一的实例到内存中.并且也解决了懒汉式中多线程的问题.解决的方式是利用了Classloader的特性.
懒汉式单例类
public class Singleton
{
private Singleton(){
}
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
return instance = new Singleton();
}else{
return instance;
}
}
}
在懒汉式中,有线程A和B,当线程A运行到第8行时,跳到线程B,当B也运行到8行时,两个线程的instance都为空,这样就会生成两个实例。解决的办法是同步:
可以同步但是效率不高:
public class Singleton{
private static Singleton instance; //声明静态的单例对象的变量
private Singleton(){} //私有构造方法
public static Singleton getSingle(){ //外部通过此方法可以获取对象
if(instance== null){
synchronized (Singleton.class) { //保证了同一时间只能只能有一个对象访问此同步块
if(instance== null){
instance= new Singleton();
}
}
}
return instance; //返回创建好的对象
}
}
为什么要在 if 语句中使用两次判断
instance== null ,这里涉及到一个名词 Double-Check Locking ,也就是双重检查锁定,为何要使用双重检查锁定呢?
考虑这样一种情况,就是有两个线程同时到达,即同时调用 GetInstance(),此时由于
instance== null ,所以很明显,两个线程都可以通过第一重的
instance== null ,进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重
instance== null ,而另外的一个线程则会在 lock 语句的外面等待。而当第一个线程执行完 new Singleton()语句后,便会退出锁定区域,此时,第二个线程便可以进入 lock 语句块,此时,如果没有第二重
instance== null 的话,那么第二个线程还是可以调用 new Singleton()语句,这样第二个线程也会创建一个 Singleton 实例,这样也还是违背了单例模式的初衷的,所以这里必须要使用双重检查锁定。
细心的朋友一定会发现,如果我去掉第一重
instance== null ,程序还是可以在多线程下完好的运行的,考虑在没有第一重
instance== null 的情况下,当有两个线程同时到达,此时,由于 lock 机制的存在,第一个线程会进入 lock 语句块,并且可以顺利执行 new Singleton(),当第一个线程退出 lock 语句块时,
instance这个静态变量已不为 null 了,所以当第二个线程进入 lock 时,还是会被第二重
instance== null 挡在外面,而无法执行 new Singleton(),所以在没有第一重
instance== null 的情况下,也是可以实现单例模式的?那么为什么需要第一重
instance== null 呢?这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()只需要执行一次就 OK 了,而如果没有第一重
instance== null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的,而如果我加上第一重
instance== null 的话,那么就只有在第一次,也就是
instance ==null 成立时的情况下执行一次锁定以实现线程同步,而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。
思路很简单,就是我们只需要同步(synchronize)初始化instance的那部分代码从而使代码既正确又很有效率。
这就是所谓的“双检锁”机制(顾名思义)。
很可惜,这样的写法在很多平台和优化编译器上是错误的。
原因在于:instance = new Singleton()这行代码在不同编译器上的行为是无法预知的。一个优化编译器可以合法地如下实现instance = new Singleton():
1. instance = 给新的实体分配内存
2. 调用Singleton的构造函数来初始化instance的成员变量
现在想象一下有线程A和B在调用getInstance,线程A先进入,在执行到步骤1的时候被踢出了cpu。然后线程B进入,B看到的是instance 已经不是null了(内存已经分配),于是它开始放心地使用instance,但这个是错误的,因为在这一时刻,instance的成员变量还都是缺省值,A还没有来得及执行步骤2来完成instance的初始化。
当然编译器也可以这样实现:
1. temp = 分配内存
2. 调用temp的构造函数
3. instance = temp
如果编译器的行为是这样的话我们似乎就没有问题了,但事实却不是那么简单,因为我们无法知道某个编译器具体是怎么做的,因为在Java的memory model里对这个问题没有定义。
双检锁对于基础类型(比如int)适用。很显然吧,因为基础类型没有调用构造函数这一步
饿汉式单例类
public class Singleton
{
private Singleton(){
}
private static Singleton instance = new Singleton();
private static Singleton getInstance(){
return instance;
}
}
饿汉式提前实例化,没有懒汉式中多线程问题,但不管我们是不是调用getInstance()都会存在一个实例在内存中
内部类式单例类
public class Singleton
{
private Singleton(){
}
private class SingletonHoledr(){
private static Singleton instance = new Singleton();
}
private static Singleton getInstance(){
return SingletonHoledr.instance;
}
}
内部类式中,实现了延迟加载,只有我们调用了getInstance(),才会创建唯一的实例到内存中.并且也解决了懒汉式中多线程的问题.解决的方式是利用了Classloader的特性.
懒汉式单例类
public class Singleton
{
private Singleton(){
}
private static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
return instance = new Singleton();
}else{
return instance;
}
}
}
在懒汉式中,有线程A和B,当线程A运行到第8行时,跳到线程B,当B也运行到8行时,两个线程的instance都为空,这样就会生成两个实例。解决的办法是同步:
可以同步但是效率不高:
双检锁写法:
private static Singleton instance; //声明静态的单例对象的变量
private Singleton(){} //私有构造方法
public static Singleton getSingle(){ //外部通过此方法可以获取对象
if(instance== null){
synchronized (Singleton.class) { //保证了同一时间只能只能有一个对象访问此同步块
if(instance== null){
instance= new Singleton();
}
}
}
return instance; //返回创建好的对象
}
}
instance== null ,这里涉及到一个名词 Double-Check Locking ,也就是双重检查锁定,为何要使用双重检查锁定呢?
考虑这样一种情况,就是有两个线程同时到达,即同时调用 GetInstance(),此时由于
instance== null ,所以很明显,两个线程都可以通过第一重的
instance== null ,进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重
instance== null ,而另外的一个线程则会在 lock 语句的外面等待。而当第一个线程执行完 new Singleton()语句后,便会退出锁定区域,此时,第二个线程便可以进入 lock 语句块,此时,如果没有第二重
instance== null 的话,那么第二个线程还是可以调用 new Singleton()语句,这样第二个线程也会创建一个 Singleton 实例,这样也还是违背了单例模式的初衷的,所以这里必须要使用双重检查锁定。
细心的朋友一定会发现,如果我去掉第一重
instance== null ,程序还是可以在多线程下完好的运行的,考虑在没有第一重
instance== null 的情况下,当有两个线程同时到达,此时,由于 lock 机制的存在,第一个线程会进入 lock 语句块,并且可以顺利执行 new Singleton(),当第一个线程退出 lock 语句块时,
instance这个静态变量已不为 null 了,所以当第二个线程进入 lock 时,还是会被第二重
instance== null 挡在外面,而无法执行 new Singleton(),所以在没有第一重
instance== null 的情况下,也是可以实现单例模式的?那么为什么需要第一重
instance== null 呢?这里就涉及一个性能问题了,因为对于单例模式的话,new Singleton()只需要执行一次就 OK 了,而如果没有第一重
instance== null 的话,每一次有线程进入 GetInstance()时,均会执行锁定操作来实现线程同步,这是非常耗费性能的,而如果我加上第一重
instance== null 的话,那么就只有在第一次,也就是
instance ==null 成立时的情况下执行一次锁定以实现线程同步,而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。
思路很简单,就是我们只需要同步(synchronize)初始化instance的那部分代码从而使代码既正确又很有效率。
这就是所谓的“双检锁”机制(顾名思义)。
很可惜,这样的写法在很多平台和优化编译器上是错误的。
原因在于:instance = new Singleton()这行代码在不同编译器上的行为是无法预知的。一个优化编译器可以合法地如下实现instance = new Singleton():
1. instance = 给新的实体分配内存
2. 调用Singleton的构造函数来初始化instance的成员变量
现在想象一下有线程A和B在调用getInstance,线程A先进入,在执行到步骤1的时候被踢出了cpu。然后线程B进入,B看到的是instance 已经不是null了(内存已经分配),于是它开始放心地使用instance,但这个是错误的,因为在这一时刻,instance的成员变量还都是缺省值,A还没有来得及执行步骤2来完成instance的初始化。
当然编译器也可以这样实现:
1. temp = 分配内存
2. 调用temp的构造函数
3. instance = temp
如果编译器的行为是这样的话我们似乎就没有问题了,但事实却不是那么简单,因为我们无法知道某个编译器具体是怎么做的,因为在Java的memory model里对这个问题没有定义。
相关文章推荐
- Java基础 - 单例(饿汉、懒汉),Runtime类,Timer,线程通信,互斥锁,线程组,线程五种状态,线程池,工厂模式,GUI,适配器设计模式
- JAVA单例设计模式(饿汉懒汉)
- Java中的单例设计模式之饿汉模式、懒汉模式
- Java设计模式中单例模式(Design Pattern):懒汉模式和饿汉模式
- Java设计模式之单例模式(懒汉/饿汉)
- java的设计模式之单利设计模式(饿汉和懒汉)
- 单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举。
- Java设计模式之单例模式的写法
- 设计模式C++实现(4)——饿汉懒汉之单例模式
- java设计模式之单列模式(懒汉,恶汉,double-check- lock)
- java设计模式之单例模式_7种写法优缺点
- java 单例模式 -饿汉 -懒汉
- 【设计模式】C++单例模式的几种写法——Java自动加载内部类对象,C++怎么破?
- [置顶] Java 多线程学习笔记(十一) 单例设计模式(延迟加载/懒汉模式)DCL解决线程安全问题
- 单例模式中的懒汉模式和饿汉模式的最优写法
- java设计模式之单例模式的七种写法
- 设计模式之单例模式:懒汉&饿汉
- 从java.lang.Runtime看单例懒汉饿汉模式的选择
- 单例设计模式(饿汉模式与懒汉模式)
- 设计模式——单例模式之“饿汉模式”与“懒汉模式”