`

你真的了解单例模式么?

阅读更多

    单例模式,很多初学者认为单例模式很简单,并且认为自己已经掌握了这种设计模式。但事实上,你真的了解单例模式了么。

 

一,单例模式的5中写法。(回字的四种写法,哈哈。)

    1,懒汉式

          (1)线程不安全的懒汉式

    public class Singleton {  
        private static Singleton instance;  
        private Singleton (){}  
      
        public static Singleton getInstance() {  
        //线程a↓ 线程b↓ 
        if (instance == null) {
       //线程a 创建一个对象 ,线程b 在这又会创建一次
            instance = new Singleton();  
        }  
        return instance;  
        }  
    }  

            初学单例模式常见的写法,懒加载,但是这种写法在实际中应用很少,因为会有线程安全问题。

 

          (2)线程安全的懒汉式

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

    ​    ​    ​用加锁的方式避免了线程安全问题,但是每一次都会判断锁,性能较低。

 

    2,饿汉式

          (1)恶汉 No1

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

    ​    ​    ​在类初始化的时候就已经创建了单例对象,导致类的初始化 不仅仅 只有调用 getInstance方法。调用静态方法或者静态变量等等。

所以没有 懒加载 的效果。

 

          (2)恶汉 No2

public class Singleton {  
    private Singleton instance = null;  
    static {  
    instance = new Singleton();  
    }  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return this.instance;  
    }  
}

    ​    ​    ​和恶汉 No1 的效果一样。都是在类的初始化的时候去创建实例

 

    3,静态内部类

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}

    ​这种方式我很喜欢,既保证了线程安全又实现了懒加载。因为当外部的类初始化的时候,不会导致内部类的初始化。只有当调用getInstance()的时候,才会创建这个单利实例。懒加载的优点:当这个instance是一个非常消耗资源的对象。我们可以让它延迟加载。在合适的时机才会创建对象。

 

    4,枚举

public enum Singleton {
	INSTANCE;
	public void doSomeThing() {
		//do someThing
	}
}

    ​这种也是非常推荐的方式,枚举是JDK1.5的特性,所以用的比较少。这种方式代码简单,并且保证的线程安全,还能防止反序列化创建新的对象,但是也同事失去了懒加载的特性。

 

    5,双重校验锁

public class Singleton {
	private volatile static Singleton singleton;
	private Singleton() {
	}
	public static Singleton getSingleton() {
		if (singleton == null) {
			synchronized (Singleton.class) {
				if (singleton == null) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
}

    ​这种写法,这种方式像是“线程安全的懒汉模式的增强版”比较麻烦。并且在JDK1.5之前,由于instance= new Singleton();这句话不是原子操作,这句话分为三步

    1.Singleton的实例分配内存。

    2.初始化Singleton的构造器

    3.instance对象指向分配的内存空间(注意到这步instance就非null了)。

由于java编译器out-of-order(允许乱序执行)以及JMM中Cache、寄存器到主内存回写顺序的规定,上面的第二点和第三点的顺序是无法保证的。也就是 不一定是 1,2,3的顺序,如过是1,3,2. 在3执行完的时候,instance就非null了。此时切换到第二个线程,直接拿走这个实例使用,就会报错。但是在JDK1.5之后 这种模式才能达到正常的效果。

具体参见:http://blog.163.com/lby67224262@126/blog/static/1714153412011121104932660

 

二,你写的单例模式真的能用嘛?

 

下面我们考虑几种会破坏单例的情况,和解决办法

1,多个classloader 获得 不同的单例对象。举个例子,一些servlet容器可能用不同的classloder 去加载servlet,如果servlet引用了同一个单例类就会产生多个实例。

    如果想你的单例类能被同一个类加载器加载,那么就必须自己制定这个类加载器。代码如下

public class Snippet {
	private static Class getClass(String classname)
			throws ClassNotFoundException {
		ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();
		if (classLoader == null)
			classLoader = Singleton.class.getClassLoader();
		return (classLoader.loadClass(classname));
	}
}

 

2,你的单例也有可能实现序列化接口,只要是实现了次接口,就有可能被反序列化。没一次反序列化创建对象,就会产生一个新的单例类的实例。解决办法如下。

public class Singleton implements java.io.Serializable {
	public static Singleton INSTANCE = new Singleton();
	protected Singleton() {
		// Exists only to thwart instantiation.
	}
	private Object readResolve() {
		return INSTANCE;
	}
}

 3,反射,还能访问private的构造方法。这样就可以创建很多对象。解决办法如下。

     

      反射时可以使用setAccessible方法来突破private的限制,我们需要做到第一点工作的同时,还需要在在 ReflectPermission("suppressAccessChecks") 权限下使用安全管理器(SecurityManager)的checkPermission方法来限制这种突破。一般来说,不会真的去做这些事情,都是通过应用服务器进行后台配置实现。具体请参考http://blog.csdn.net/yaerfeng/article/details/7103397

 

4,涉及到跨JVM(集群、远程EJB等)时 也会出现产生多个单例类的对象的问题。


      在一个多元 JVM 环境中,每个 JVM 拥有自己对于该单例对象的拷贝,这将导致很多问题,尤其是在对于资源的访问需要受限和加锁的集群的环境中。

      解决方式可以通过应用服务器提供的API 或者第三方的工具。详情参考http://blog.csdn.net/defonds/article/details/12705677

 

 

 

分享到:
评论

相关推荐

    单例模式案例和笔记,通过案例来了解单例模式

    Java一共有23种设计模式,单例模式就是指一个类只有一个实例,且该类能自行创建这个实例的一种模式。可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。比如咱们电脑...

    Java单例模式深入理解

    不同单例模式的详细讲解,了解使用单例的最佳方式。

    你真的了解java单例模式了吗?

    主要介绍了你真的了解java单例模式了吗?实际上单例模式有着好几个变种,并且多线程中涉及到线程安全问题,,需要的朋友可以参考下

    单例模式讲解案例

    单例模式的简单了解文档,以及代码中怎么使用单例模式。

    php基于单例模式编写PDO类

    引入了单例模式来保证在全局调用中不会重复实例化这个类,降低系统资源的浪费,一个对象(在学习设计模式之前,需要比较了解面向对象思想)只负责一个特定的任务。 php的应用主要在于数据库应用, 所以一个应用中会...

    JAVA设计模式之单例模式

    JAVA设计模式之单例模式。 一篇文章带你快速了解!

    ios-简易了解GCD的单例模式.zip

    简易了解GCD的单例模式

    设计模式之单例模式.md

    设计模式之单例模式,单列模式的几种实现形式,以及其优缺点,还有就是示例,对初步了解单列模式的有所帮助

    Java 单例模式线程安全问题

    主要介绍了Java 单例模式线程安全问题的相关资料,希望通过本文大家能了解掌握单例模式中线程安全的使用方法,需要的朋友可以参考下

    基于JavaScript实现单例模式

    首先,了解一下什么是单例模式,这里我直接把软件开发网中的定义给copy过来: 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。...

    java单例模式

    对于java的单例模式进行了详细透彻的分析与实践,方便使用者了解单例模式的本质

    php设计模式之单例模式实例分析

    单例模式(职责模式): 简单的说,一个对象(在学习设计模式之前,需要比较了解面向对象思想)只负责一个特定的任务; 单例类: 1、构造函数需要标记为private(访问控制:防止外部代码使用new操作符创建对象),...

    【Java设计模式】你对单例模式了解多少,一文深入探究

    目录单例模式懒汉式单例模式未初始化问题解决Double Check 双重检查方案一:不让第二步和第三步重排序-DoubleCheck方案二:基于类初始化-静态内部类饿汉式饿汉式与懒汉式最大区别序列化破坏单例模式原理枚举单例基于...

    php基于单例模式编写PDO类.zip

    引入了单例模式来保证在全局调用中不会重复实例化这个类,降低系统资源的浪费,一个对象(在学习设计模式之前,需要比较了解面向对象思想)只负责一个特定的任务。 php的应用主要在于数据库应用, 所以一个应用...

    Java模式设计之单例模式

     我们总结分析单例模式的时候,了解到单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个事例;三是它必须自行向整个系统提供这个实例。在下面的对象图中,有一个"单例对象",而"客户甲"、...

    c# 单例模式的实现方法

    主要介绍了c# 单例模式的实现方法,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下

    Java单例模式实现静态内部类方法示例

    主要介绍了Java单例模式实现静态内部类方法示例,涉及构造函数私有化等相关内容,需要的朋友可以了解下。

    C++单例模式的实例详解

    且不说公司企业在招聘的时候为了考察员工对设计的了解和把握,考的最多的就是单例模式。 单例模式解决问题十分常见,我们怎样去创建一个唯一的变量(对象)?在基于对象的设计中我们可以通过创建一个全局变量(对象...

    JAVA多线程并发下的单例模式应用

    单例模式应该是设计模式中比较简单的一个,也是非常常见的,但是在多线程并发的环境下使用却是不那么简单了,今天给大家分享一个我在开发过程中遇到的单例模式的应用。

    PHP基于单例模式编写PDO类的方法

    二、为什么要使用PHP单例模式?  1、php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作, 使用单例模式, 则可以避免大量的new 操作消耗的资源。  2、如果系统中需要有一个类来全局控制某些配置...

Global site tag (gtag.js) - Google Analytics