单例模式详解
2016-03-31 15:44
417 查看
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。在多线程更是要注意。
参考:
JAVA设计模式之单例模式
单例模式与双重检测
The “Double-Checked Locking is Broken” Declaration
Java多线程编程环境中单例模式的实现
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例。
要实现线程安全可以对getInstance这个方法改造,保证了懒汉式单例的线程安全。
这种办法很影响性能:每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了
*有出错成的可能,以A、B两个线程为例:
A、B线程同时进入了第一个if判断
A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
*
这样的代码缺点是:第一次加载类的时候会连带着创建Singleton实例,这样的结果与我们所期望的不同,因为创建实例的时候可能并不是我们需要这个实例的时候。同时如果这个Singleton实例的创建非常消耗系统资源,而应用始终都没有使用Singleton实例,那么创建Singleton消耗的系统资源就被白白浪费了。
JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。此外该方法也只会在第一次调用的时候使用互斥机制,这样就解决了”静态同步方法”的问题。最后instance是在第一次加载SingletonContainer类时被创建的,而SingletonContainer类则在调用getInstance方法的时候才会被加载,因此也实现了惰性加载。
参考:
JAVA设计模式之单例模式
单例模式与双重检测
The “Double-Checked Locking is Broken” Declaration
Java多线程编程环境中单例模式的实现
单例模式的特点:
1、单例类只能有一个实例。2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
懒汉式单例
/** * 懒汉式,在第一次调用的时候实例化自己<p> * 本身是线程不安全的,并发环境下很可能出现多个Singleton实例。 * 如果在静态工厂上加了同步,虽然线程安全了,但是每次都要同步, * 会影响性能,毕竟99%的情况下是不需要同步的 */ public class LazySingleton{ private static LazySingleton singleton = null; private LazySingleton(){ } //静态工厂方法 。此方法设置成 静态同步方法就可线程安全 // public static synchronized LazySingleton getInstance(){ public static LazySingleton getInstance(){ if(singleton == null){ singleton = new LazySingleton(); } return singleton; } /* //另外一种方法(双重检查锁定) public static LazySingleton getInstance(){ if(singleton == null){ synchronized (LazySingleton.class) { if(singleton == null){ singleton = new LazySingleton(); } } } return singleton; } */ }
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例。
要实现线程安全可以对getInstance这个方法改造,保证了懒汉式单例的线程安全。
1.将getInstance()方法设置成 静态同步方法就可线程安全。
public static synchronized LazySingleton getInstance(){ if(singleton == null){ singleton = new LazySingleton(); } return singleton; }
这种办法很影响性能:每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了
2.将getInstance()方法内部添加双重检查锁定
public static LazySingleton getInstance(){ if(singleton == null){ //注意这里不能写 synchronized(this) 因为这是静态方法内部,this从哪里来 synchronized (LazySingleton.class) { if(singleton == null){ singleton = new LazySingleton(); } } } return singleton; }
*有出错成的可能,以A、B两个线程为例:
A、B线程同时进入了第一个if判断
A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
*
饿汉式单例
/** * 饿汉式<p> * 饿汉式单例类.在类初始化时,已经自行实例化 * 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。 */ public class HungrySingleton { private static HungrySingleton singleton = new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getInstance(){ return singleton; } }
这样的代码缺点是:第一次加载类的时候会连带着创建Singleton实例,这样的结果与我们所期望的不同,因为创建实例的时候可能并不是我们需要这个实例的时候。同时如果这个Singleton实例的创建非常消耗系统资源,而应用始终都没有使用Singleton实例,那么创建Singleton消耗的系统资源就被白白浪费了。
静态内部类式
/** * 静态内部类<p> * 相比懒汉式,实现了线程安全,又避免了同步带来的性能影响 */ public class InternalSingleton { private InternalSingleton(){} public static InternalSingleton getInstance(){ return SingletonHolder.INSTANCE; } private static class SingletonHolder{ private final static InternalSingleton INSTANCE = new InternalSingleton(); } }
JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。此外该方法也只会在第一次调用的时候使用互斥机制,这样就解决了”静态同步方法”的问题。最后instance是在第一次加载SingletonContainer类时被创建的,而SingletonContainer类则在调用getInstance方法的时候才会被加载,因此也实现了惰性加载。
测试:
import org.junit.Test; public class GetSingletonInstance { @Test public void getLazySingletonInstance() { LazySingleton singleton1 = LazySingleton.getInstance(); LazySingleton singleton2 = LazySingleton.getInstance(); if(singleton1 == singleton2){ System.out.println("是同一个实例!"); }else{ System.out.println("不是同一个实例!"); } } @Test public void getHungrySingletonInstance() { HungrySingleton singleton1 = HungrySingleton.getInstance(); HungrySingleton singleton2 = HungrySingleton.getInstance(); if(singleton1 == singleton2){ System.out.println("是同一个实例!"); }else{ System.out.println("不是同一个实例!"); } } @Test public void getInternalSingletonInstance() { InternalSingleton singleton1 = InternalSingleton.getInstance(); InternalSingleton singleton2 = InternalSingleton.getInstance(); if(singleton1 == singleton2){ System.out.println("是同一个实例!"); }else{ System.out.println("不是同一个实例!"); } } }
结果:
是同一个实例! 是同一个实例! 是同一个实例!
结论
优先使用 静态内部类式相关文章推荐
- 举例讲解C#编程中对设计模式中的单例模式的运用
- php设计模式之单例模式实例分析
- PHP基于单例模式实现的数据库操作基类
- JavaScript编程的单例设计模讲解
- C#设计模式之单例模式实例讲解
- Javascript实现单例模式
- JS模式之单例模式基本用法
- 深入理解JavaScript系列(25):设计模式之单例模式详解
- 使用设计模式中的单例模式来实现C++的boost库
- .NET c# 单体模式(Singleton)
- Java单例模式、饥饿模式代码实例
- java设计优化之单例模式
- Android源码学习之单例模式应用及优点介绍
- C++设计模式之单例模式
- C#窗口实现单例模式的方法
- Java线程安全中的单例模式
- PHP单例模式详细介绍
- PHP 面向对象程序设计(oop)学习笔记(三) - 单例模式和工厂模式
- php实现singleton()单例模式实例
- 三种单例模式与Object祖先类