概念:
在Java应用程序中,一个类Class只有一个实例存在
运用:
1)系统资源,如文件路径,数据库链接,系统常量等
2)全局状态化类,类似AutomicInteger的使用
优缺点:
1)节省内存有利于垃圾回收
2)只能使用在特定的环境下,受限制于JVM和容器
单例作用范围的前提是在一个ClassLoad下。所以像分布式应用EJB就要用其它的方式来解决单例问题。
Demo:
分别列出多种实现方式,各分析其优缺点
1)静态成员直接初始化,或者在静态代码块初始化都可以
- class Singleton{
- private Singleton(){}
- private static final Singleton singleton = new Singleton();
- public static Singleton getInstance(){return singleton;}
- }
该实现只要在一个ClassLoad下就会提供一个对象的单例。但是美中不足的是,不管该资源是否被请求,它都会创建一个对象,占用jvm内存。从lazy initialization思想出发,出现了下2的写法
2)根据lazy initialization思想,使用到时才初始化。
- class Singleton{
- private Singleton(){}
- private static Singleton singleton ;
- public static synchronized Singleton getInstance(){
- if(singleton==null)
- singleton = new Singleton();
- return singleton;
- }
- }
该实现方法加了同步锁,可以有效防止多线程在执行getInstance方法得到2个对象。
缺点:只有在第一次调用的时候,才会出现生成2个对象,才必须要求同步。而一旦singleton 不为null,系统依旧花费同步锁开销,有点得不偿失。
因此再改进出现写法3
3)在2的基础上改进,改进标准:尽量减少锁资源(主要体现在执行时间,所占内存等)
- class Singleton{
- private Singleton(){}
- private static Singleton singleton ;
- public static Singleton getInstance(){
- if(singleton==null)//1
- synchronized(Singleton.class){//2
- singleton = new Singleton();//3
- }
- return singleton;
- }
- }
这种写法减少了锁开销,但是在如下情况,却创建了2个对象:
a:线程1执行到1挂起,线程1认为singleton为null
b:线程2执行到1挂起,线程2认为singleton为null
c:线程1被唤醒执行synchronized块代码,走完创建了一个对象
d:线程2被唤醒执行synchronized块代码,走完创建了另一个对象
所以看出这种写法,并不完美。
4)为了解决3存在的问题,引入双重检查锁定
- public static Singleton getInstance(){
- if(singleton==null)//1
- synchronized(Singleton.class){//2
- if(singleton==null)//3
- singleton = new Singleton();//4
- }
- return singleton;
- }
在同步锁代码块内部,再判断一次对象是否为null,为null才创建对象。这种写法已经接近完美:
a:线程1执行到1,已经进入synchronized的时候,线程挂起,线程1占有Singleton.class资源锁;
b:线程2执行到1,当它准备synchronized块时,因为Singleton.class被占用,线程2阻塞;
c:线程1被唤醒,判断出对象为null,执行完创建一个对象
d:线程2被唤醒,判断出对象不为null,不执行创建语句
如此分析,发现似乎没问题。
但是实际上并不能保证它在单处理器或多处理器上正确运行;
问题就出现在singleton = new Singleton()这一行代码。它可以简单的分成如下三个步骤:
mem= singleton();//1
instance = mem;//2
ctorSingleton(instance);//3
这行代码先在内存开辟空间,赋给singleton的引用,然后执行new 初始化数据,但是注意初始化是要消耗时间。如果此时线程3在执行步骤1的时候,发现singleton 为非null,就直接返回,那么线程3返回的其实是一个没构造完成的对象。
我们期望1,2,3 按照反序执行,但是实际jvm内存模型,并没有明确的有序指定。
这归咎于java的平台的内存模型允许“无序写入”。
5)在4的基础上引入volatile
代码如下:
- class Singleton{
- private Singleton(){}
- private static volatile Singleton singleton ;
- public static Singleton getInstance(){
- if(singleton==null)//1
- synchronized(Singleton.class){//2
- if(singleton==null)//3
- singleton = new Singleton();
- }
- return singleton;
- }
- }
Volatile 变量具有 synchronized
的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。
而volatile使用时有明确的规定:
- 对变量的写操作不依赖于当前值;
- 该变量没有包含在具有其他变量的不变式中;
—— 只有在状态真正独立于程序内其他内容时才能使用 volatile。
但是5的写法,虽然理论上似乎可以解决无序写入问题。实际上并非如此。
(我个人觉得这里对volatile语法说的不够详细,想知道详细的可以看这篇转帖Java 理论与实践: 正确使用 Volatile 变量)
小结:
1)使用同步锁方法,内部锁存在不安全。
2)静态成员直接初始化。
本人学习java单例模式(Singleton pattern)做的笔记,拿出来分享给像我这样的菜鸟们。希望对菜鸟们有一点点的帮助。
本人参考了:Balan的文章 Java Singleton 实用教程(附源码)
原文地址:http://balan.iteye.com/blog/164873
一、定义
单例模式(Singleton pattern):确保一个类只有一个实例,并提供一个全局的访问点。
这个定义包含两层意思:
第一:我们把某个类设计成自己管理的一个单独实例,同时也要避免其他类再自行产生实例。要想取得单个实例,通过单例类是唯一的途径。
第二:我们必需提供对这个实例的全局访问点:当你需要实例时,向类查询,它会给你返回单个实例。
注意:单例模式确保一个类只有一个实例,是指在特定系统范围内只能有一个实例。有时在某些情况下,使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器,跨JVM的。
1。某个框架容器内:如Spring IOC容器,可以通过配置保证实例在容器内的唯一性。
2。再如单一JVM中、单一类加载器加载类的情况可以保证实例的唯一性。
如果在两个类加载器或JVM中,可能他们有机会各自创建自己的单个实例,因为每个类加载器都定义了一个命名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类会被加载多次。如果这样的事情发生在单例上,就会产后多个Singleton并存的怪异现象。所以如果你的程序有多个类加载,同时你又使用了单例模式,请一定要小心。有一个解决办法是,自行给单例类指定类加载器(指定同一个类加载器)。
二、用处
有一些对象其实我们完全只需要一个即可,如:线程池(threadpool)、缓存(cache)、注册表(registry)的对象、设备的驱动程序的对象等等。事实上,这些类的对象只能有一个实例,如果制造出多个实例,就会导致许多问题的产生,例如:程序的行为异常、资源的过量使用、产生不一致的结果等等。JavaSingleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。我们常常看到工厂模式中类装入器(classloader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。
三、Java Singleton模式常见的几种形式
一)。使用立即创建实例,而不用延迟实例化的做法
1。使用全局变量
- //Singleton with final field
- public class Singleton {
- public static final Singleton uniqueInstance = new Singleton();
- private Singleton(){
- }
- //...Remainder omitted
- }
在这种方法中,公有静态成员是一个final域(保证了总是包含相同的对象引用)。私有构造函数仅被调用一次,用来实例化公有的静态final域Singleton.uniqueInstace。由于缺少公有的或者受保护的构造函数,所有保证了Singleton的全局唯一性:一旦Singleton类被实例化之后,只有一个Singleton实例存在——不多也不少。使用此Singleton类的程序员的任何行为都不能改变这一点。
2。使用公有的静态工厂方法
- //Singleton with static factory
- public class Singleton {
- private static Singleton uniqueInstance = new Singleton();
- private Singleton(){
- }
- public static Singleton getInSingleton(){
- return uniqueInstance;
- }
- //...Remainder omitted
- }
第二种方法提供了一个公有的静态工厂方法,而不是公有的静态final域。利用这个做法,我们依赖JVM在加载这个类时马上创建此类唯一的一个实例。JVM保证任何线程访问uniqueInstance静态变量之前,一定先创建此实例。
二)。使用延迟实例化的做法(使用公有的静态工厂方法)
1。非线程安全的
- public class Singleton {
- private static Singleton uniqueInstance ;
- private Singleton(){
- }
- public static Singleton getInSingleton(){
- if(uniqueInstance == null){
- uniqueInstance = new Singleton();
- }
- return uniqueInstance;
- }
- //...Remainder omitted
- }
先利用一个静态变量uniqueInstance来记录Singleton类的唯一实例,当我们要使用它的实例时,如果它不存在,就利用私有的构造器产生一个Singleton类的实例并把它赋值到uniqueInstance静态变量中。而如果我们不需要使用这个实例,它就永远不会产生。这就是"延迟实例化(lazy instantiaze)"。但上面这段程序在多线程环境中是不能保证单个实例的。分析如下:
时间点 | 线程1 | 线程2 | uniqueInstance的值 |
1 | 线程1,2同时访问Singleton.getInstance()方法 | ||
2 | 进入Singleton.getInstance()方法 | null | |
3 | 进入Singleton.getInstance()方法 | null | |
4 | 执行if(uniqueInstance == null)判断 | null | |
5 | 执行if(uniqueInstance == null)判断 | null | |
6 | 执行uniqueInstance = new Singleton() | Singleton1 | |
7 | 执行uniqueInstance = new Singleton() | Singleton2 | |
8 | 执行return uniqueInstance; | Singleton1 | |
9 | 执行return uniqueInstance; | Singleton2 |
如上分析所示,它已产生了两个Singleton实例。
2。多线程安全的
- public class Singleton {
- private static Singleton uniqueInstance ;
- private Singleton(){
- }
- public synchronized static Singleton getInSingleton(){
- if(uniqueInstance == null){
- uniqueInstance = new Singleton();
- }
- return uniqueInstance;
- }
- //...Remainder omitted
- }
通过给getInstance()方法增加synchronized关键字,也就是给getInstance()方法线程加锁,迫使每次只能有一个线程在进入这个方法,这样就可以解决上面多线程产生的灾难了。但加锁的同步方法可能造成程序执行效率大幅度下降,如果你的程序对性能的要求很高,同时你的getInstance()方法调用的很频繁,这时可能这种设计也不符合程序要求了。其实这种加锁同步的方法用在这确实有一定的问题存在,因为对Singleton类来说,只有在第一次执行getInstance()方法时,才真正的需要对方法进行加锁同步,因为一旦第一次设置好uniqueInstance变量后,就不再需要同步这个方法了。之后每次调用这个方法,同步反而成了一种累赘。
3。 用"双重检查加锁",在getInstance()方法中减少使用同步:
- public class Singleton {
- // volatile关键字确保当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量
- private volatile static Singleton uniqueInstance;
- private Singleton() {
- }
- public static Singleton getInSingleton() {
- if (uniqueInstance == null) {// 检查实例,如是不存在就进行同步代码区
- synchronized (Singleton.class) {// 对其进行锁,防止两个线程同时进入同步代码区
- if (uniqueInstance == null) {// 双重检查,非常重要,如果两个同时访问的线程,当第一线程访问完同步代码区后,生成一个实例;当第二个已进入getInstance方法等待的线程进入同步代码区时,也会产生一个新的实例
- uniqueInstance = new Singleton();
- }
- }
- }
- return uniqueInstance;
- }
- // ...Remainder omitted
- }
对于双重检查加锁(Double-Checked Locking)有一篇文章解释的很深入:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
三。Sington类的序列化
为了使Singleton类变成可序列化的(serializable),仅仅实现Serializable接口是不够的。为了维护Singleton的单例性,你必须给Singleton类提供一个readResolve方法,否则的话,一个序列化的实例,每次反序列化的时候都会产生一个新的实例。Singleton也不会例外。
如下所示:
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.ObjectStreamException;
- import java.io.Serializable;
- //Singleton with final field
- public class Singleton implements Serializable{
- private static final long serialVersionUID = 5765648836796281035L;
- public static final Singleton uniqueInstance = new Singleton();
- private Singleton(){
- }
- //...Remainder omitted
- public static void main(String[] args) throws Exception{
- //序列化
- ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\Singleton.obj"));
- Singleton singleton = Singleton.uniqueInstance;
- objectOutputStream.writeObject(singleton);
- objectOutputStream.close();
- //反序列化
- ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\Singleton.obj"));
- Singleton singleton2 = (Singleton)objectInputStream.readObject();
- objectInputStream.close();
- //比较是否原来的实例
- System.out.println(singleton==singleton2);
- }
- }
输出结果为:false
解决方法是为Singleton类增加readResolve()方法:
- //readResolve 方法维持了Singleton的单例属性
- private Object readResolve() throws ObjectStreamException{
- return uniqueInstance;
- }
再进行测试:输出结果为true
反序列化之后新创建的对象会先调用此方法,该方法返回的对象引用被返回,取代了新创建的对象。本质上,该方法忽略了新建对象,仍然返回类初始化时创建的那个实例。