【转自:www.chenyudong.com/archives/java-singleton.html】作者:东东东 陈煜东
在设计模式中,单例模式(Singleton)是最长见得一种设计模式之一。什么是单例模式呢?就是在整个系统中,只有一个唯一存在的实例。这样的情况可以干什么用呢?比如可以统计网站的访问量,一些连接池(数据库连接池等)。
一个最简单的单例模式 – 饿汉模式
那么怎么能保证只有一个对象的存在呢?首先得有一个static的实例,这个方法保证了一个class只有一个实例。还得防止外界使用构造器来new一个实例。
1
2
3
4
5
|
//一个没有封装的单例模式 public class Singleton {
public static final Singleton singleton = new Singleton();
private Singleton(){}
} |
外界就可以使用Singleton.singleton
这样的方法来调用了。但是这样存在的问题就是分装不够好,添加一个方法,返回singleton的引用。如下
1
2
3
4
5
6
|
//最简单的封装单例改进版。饿汉模式 public class Singleton {
public static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){ return singleton; }
} |
当代码写到这,我们终于可以松一口气了,原来单例模式也很简单呀,就这么几行的代码。对,其实这个是最简单的一种方式,能够应付大部分的场景的。
不过,其他class在引用Singleton而不使用的时候,虚拟机会自动加载这个类,并且实例化这个对象(这点知道Java虚拟机的类加载就会了解那么一些)。于是我们就有了下面的写法。
延迟实例化(懒汉模式) – 在调用时进行实例化
经常能看见其他的单例模式会教下面的代码,这样的人估计是从《设计模式 – 可复用面向对象软件的基础》那本书看来的。这本书使用的是C++语言写的,然后就将其转到了Java平台来。
首先他们会说我们的代码应该先这样,在调用的时候,发现为null,再进行实例化该类。
01
02
03
04
05
06
07
08
09
10
|
public class Singleton {
public static final Singleton singleton = null ;
private Singleton(){}
public static Singleton getInstance(){
if (singleton == null ){ //如果singleton为空,表明未实例化
singleton = new Singleton();
}
return singleton;
}
} |
然后他们还会说,这样在多线程
的情况下会出现这样的情况:两个进程都进入到if (singleton == null),于是两个线程都对这个进行实例化,这样就出问题啦。
所以他们又说应该使用synchronize关键字,在实例化之前,进行加锁行为。于是又产生了一下的代码
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public class Singleton {
public static final Singleton singleton = null ;
private Singleton(){}
public static Singleton getInstance(){
if (singleton == null ){ //如果singleton为空,表明未实例化
synchronize (Singleton. class ){
if ( singleton == null ) { // double check 进来判断后再实例化。
singleton = new Singleton();
}
}
return singleton;
}
} |
他们就会说,他们的这个代码使用了double check (双重检测)
,以防止这样的情况:当两个线程执行完第一个singleton == null 后等待锁, 其中一个线程获得锁并进入synchronize后,实例化了,然后退出释放锁,另外一个线程获得锁,进入又想实例化,会判断是否进行实例化了,如果存在,就不进行实例化了。
他们会说,这样的代码perfect,很完美,既解决了多线程带来的问题,又解决了延迟实例化的方式。
我觉得这样的代码只是将C++版的单例模式复制到Java平台,没有Java的特色。第一个就是一个Java特色的代码,它解决了多线程的问题,因为JLS(Java Language Specification)中规定了一个类(Singleton.class)只会被初始化一次,但是不能解决延迟实例化的情况。如需要延迟实例化,可以看下面的方法,使用内部类来实现。
使用内部类的单例模式 (懒汉模式)
刚才说了,第一个不能解决延迟实例化Singleton对象的问题。所以我们使用内部类来进行,看看代码。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
//一个延迟实例化的内部类的单例模式 public final class Singleton {
//一个内部类的容器,调用getInstance时,JVM加载这个类
private static final class SingletonHolder {
static final Singleton singleton = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
|
我们来看看这个代码。首先,其他类在引用这个Singleton的类时,只是新建了一个引用,并没有开辟一个的堆空间存放(对象所在的内存空间)。接着,当使用Singleton.getInstance()
方法后,Java虚拟机(JVM)会加载SingletonHolder.class
(JLS规定每个class对象只能被初始化一次),并实例化一个Singleton对象。
这样做就可以解决前面说的对线程多次实例化对象
和延迟实例化对象
的问题了。
缺点:不过你会使用这样复杂的方式嘛?代码那么多。只是为了延迟一个对象的实例化,引入另外一个class。就为了延迟那么一次对象延迟的实例化,延缓Java的heap堆内存。为此付出的代价是引入一个class,需要在Java的另外一个内存空间(Java PermGen 永久代内存,这块内存是虚拟机加载class文件存放的位置)占用一个大块的空间。
还存在的一些问题
好了,在许多情况其实用第一种方法就差不多可以了。在特殊情况下,还是存在着一些问题。
用反射生成对象
如果使用Java的反射机制来生成对象的话,那么单例模式就会被破坏。
1
2
3
4
5
|
//使用反射破坏单例模式 Class c = Class.forName(Singleton. class .getName());
Constructor constructor = c.getDeclaredConstructor(); constructor.setAccessible( true );
Singleton singleton = (Singleton)ct.newInstance(); |
对于用反射破坏单例模式的,是不对其进行代码保护的,即由此造成的后果,由写反射的构建单例实例的人负责。所以我们就不用担心反射带来的问题了。
分布式上,解决单例模式
对于分布式上的单例模式,应该使用RMI(Remote Method Invocation 远程方法调用)来进行。或者使用web serivce,在单个服务器上存在单例,其他的机器使用SOAP协议进行访问。
不同的CLASSLOADER(类加载器)加载SINGLETON
在不同的ClassLoader加载Singleton,他们是不一样的。就像在不同的package中相同的类名,他们是不同的类。同样的,在不同的ClassLoader上加载的类,他们尽管代码一样,还是属于不同的类。
这样,需要自己写classloader,保证Singleton.class的加载唯一。
参考文章:http://www.oschina.net/question/9709_102019
单例模式的对象是否会被JVM回收?
对于这个问题,在还没实例化单例的时候,对象不存在,单实例化后,那么singleton的引用就存在的,只要Singleton.class存在虚拟机中。那么什么时候Singleton.class会被回收呢?对于这个问题,牵扯了许多的问题。因为Singleton对象存在,所以Singleton.class就也存在,这样形成了相互依赖,所以不会被JVM垃圾回收。
网上有个文章验证了他的想法 http://blog.csdn.net/zhengzhb/article/details/7331354
使用反序列化生成对象
如果你的Singleton序列化了,那么通过反序列化方式可以生成一个对象。通过增加readResolve方法来解决。如下
1
2
3
4
5
6
7
|
//最简单的封装单例改进版。饿汉模式。序列化及反序列化解决 public class Singleton implements java.io.Serializable {
public static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){ return singleton; }
private Object readResolve() { return singleton; }
} |
使用CLONE()克隆对象
Object的clone()
方法默认是抛出CloneNotSupportedException
异常的,所以只要不覆盖该方法,调用的时候,也会抛出异常。
effective java作者提到用enum枚举来实现单例模式。
扩展阅读:使用enum来进行Java的单例模式:http://coolxing.iteye.com/blog/1446648
相关推荐
在Java中,有多种实现单例模式的方法,每种都有其特点和适用场景。接下来,我们将深入探讨这些实现方式。 首先,我们来看**懒汉式(Lazy Initialization)**。这种实现方式是在类被首次请求时才创建单例对象,延迟...
### Java 单例模式详解 #### 一、什么是单例模式? 单例模式是一种常用的软件设计模式,在这种模式中,一个类只能拥有一个实例,并且该类必须自行创建并提供这个实例。通常,单例模式用于确保某个类在整个应用程序...
Java中的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于控制资源的访问,比如数据库连接池、线程池或者日志对象等。本篇文章将深入探讨如何在Java中...
Java 单例模式 懒汉模式 //懒汉式 多线程中不可以保证是一个对象
Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式Java SE程序 单例模式...
### 使用Java单例模式实现一个简单的日志记录器 #### 一、单例模式简介 单例模式是一种常用的软件设计模式,在该模式中,一个类只能创建一个实例,并且提供了一个全局访问点来访问该实例。单例模式的主要优点包括...
Java单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供全局访问点。这种模式在需要频繁创建和销毁对象的场景中,或者当对象昂贵时(如数据库连接),能够节省系统资源,提高效率。本篇文章将深入探讨...
### JAVA单例模式的几种实现方法 #### 一、饿汉式单例类 饿汉式单例类是在类初始化时就已经完成了实例化的操作。这种实现方式简单且线程安全,因为实例化过程是在编译期间完成的,不会受到多线程的影响。 **代码...
### Java单例模式应用研究 #### 一、单例模式概述 单例模式(Singleton Pattern)作为一种最基本的创建型设计模式,其主要目的是控制一个类的实例化过程,确保在整个应用程序中仅存在一个实例,并且该实例能够被全局...
Java单例模式是一种设计模式,它允许在程序中创建唯一一个类实例,通常用于管理共享资源,例如数据库连接、线程池或者配置对象等。单例模式的核心在于限制类的构造函数,确保类只能被初始化一次,从而实现全局唯一的...
通过单例模式实例化获取propertyUtil 工具包实例,高效加载配置文件,java语言编写。通过单例模式实例化获取propertyUtil 工具包实例,高效加载配置文件,java语言编写。通过单例模式实例化获取propertyUtil 工具包...
实用Java的单例模式,实用于Java学习者 单例模式 单例模式
在Java编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象的场景中尤其有用,因为它可以节省系统资源并确保对象间的协调一致。以下是...
Java单例模式及实现 Java单例模式是一种常见的设计模式,确保某一个类只有一个实例,而且向这个系统提供这个实例。单例模式可以分为三种:懒汉式单例、饿汉式单例、登记式单例。 单例模式的要点 1. 某个类只能有...
Java单例模式是一种设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在数据库连接管理中,使用单例模式能有效控制资源,避免频繁创建和关闭数据库连接导致的性能损失和资源浪费。以下是对Java单例模式...
Java单例模式是一种常见的设计模式,它在软件工程中用于控制类的实例化过程,确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统资源管理、缓存、日志记录等方面应用广泛。下面我们将深入探讨Java单例...