在Java程序中,要说用到的设计模式中,单例(Singleton)模式可能是使用最多的一种设计模式了。一些管理器和控制器常被设计成单例模式,在Spring中, 一个Component就只有一个实例在Java-Web中, 一个Servlet类只有一个实例。
Java中单例(Singleton)模式是一种使用广泛的设计模式,单例模式的主要作用是保证在Java应用程序中,某个类只有一个实例存在。单例模式好处是它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间,并且能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能作用于整个应用程序,而且起到了全局统一控制管理的作用,那么单例模式是一个非常值得考虑的选择。
单例模式的写法有很多种,其中常用的写法就是饿汉模式、懒汉模式及对象序列化模式。在介绍单例模式的设计前,先简单介绍静态内部类的加载机制。一些写法可能不足,欢迎大家多多指教。
Java中静态内部类的加载机制,代码如下:
/**
* 内部类的加载机制测试
*/
public class InnerClassLoadTest {
static {
System.out.println("Load outer class,className:" + InnerClassLoadTest.class.getName());
}
public void doThing() {
System.out.println("Call outer class method,methodName:doThing()");
}
/**
* 定义内部静态类
*/
static class InnerClass {
static {
System.out.println("Load static inner class,className:" + InnerClass.class.getName());
}
static void doThing() {
System.out.println("Call static inner class method,methodName:doThing()");
}
}
public static void main(String[] args) {
// 测试:此刻其内部类是否也会被加载?
InnerClassLoadTest outerTest = new InnerClassLoadTest();
outerTest.doThing();
System.out.println("***************************调用静态内部类的的静态方法***************************");
// 调用内部类的静态方法,此时才会初始化静态内部类
InnerClassLoadTest.InnerClass.doThing();
}
// 运行结果:
// Load outer class,className:com.innerclass.loader.InnerClassLoadTest
// Call outer class method,methodName:doThing()
// ***************************调用静态内部类的的静态方法***************************
// Load static inner
// class,className:com.innerclass.loader.InnerClassLoadTest$InnerClass
// Call static inner class method,methodName:doThing()
// 结论:
// (1) 加载一个类时,其内部静态类不会同时被加载
// (2) 一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生
}
了解Java的内部静态类加载机制,对于后面了解懒汉式单例模式有极大的帮助。
代码如下:
/**
* 单例设计模式-饿汉式
*
* 优点:线程安全
*
* 缺点:很明显,类加载的时候就实例化对象了,浪费空间
*/
public class HungrySingletonDemo {
private static HungrySingletonDemo instance = new HungrySingletonDemo();
public static HungrySingletonDemo getInstance() {
return instance;
}
private HungrySingletonDemo() {
System.out.println("instantiation hungry singleton");
}
public void doThing() {
System.out.println("this is a hungry singleton demo");
}
public static void main(String[] args) {
HungrySingletonDemo.getInstance().doThing();
}
// 运行结果:
// instantiation hungry singleton
// this is a hungry singleton demo
}
这种实现方式是线程安全的,但是确定也很明显,类加载的时候就实例了对象,浪费空间。这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。如果单例占用的内存比较大,或者单例在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。
代码如下:
/**
* 单例设计模式-懒汉式
*
* 缺点:线程不安全
*
*/
public class LazySingletonDemo {
private static LazySingletonDemo instance;
public static LazySingletonDemo getInstance() {
if (instance == null) {
instance = new LazySingletonDemo();
}
return instance;
}
private LazySingletonDemo() {
System.out.println("instantiation lazy singleton");
}
public void doThing() {
System.out.println("this is a lazy singleton demo");
}
public static void main(String[] args) {
LazySingletonDemo.getInstance().doThing();
}
}
这种写法,虽然实现了延迟加载,但是却不是线程安全的,在大量的并发场合下,可能会创建多个实例。为了实现线程安全,就在getInstance()方法内加载synchronized修饰符,于是就有了线程安全的懒汉单例模式。
代码如下:
/**
* 单例设计模式-线程安全的懒汉式
*
* 缺点:实例化前使用synchronized进行线程锁安全,性能上会大打折扣,因为syncrhonized会造成线程阻塞
*
*/
public class LazySingletonSynchronizedDemo {
private static LazySingletonSynchronizedDemo instance;
public static LazySingletonSynchronizedDemo getInstance() {
synchronized (LazySingletonSynchronizedDemo.class) {
if (instance == null) {
instance = new LazySingletonSynchronizedDemo();
}
}
return instance;
}
private LazySingletonSynchronizedDemo() {
System.out.println("instantiation synchronized lazy singleton");
}
public void doThing() {
System.out.println("this is a synchronized lazy singleton demo");
}
public static void main(String[] args) {
LazySingletonSynchronizedDemo.getInstance().doThing();
}
}
此写法的优点是实现了线程安全,但实例化前使用synchronized进行线程锁安全,性能上会大打折扣,因为syncrhonized会造成线程阻塞。为了进一步提高单例模式的性能,于是提出了高性能的线程安全模式。
代码如下:
/**
* 单例设计模式-双重校验锁的线程安全的懒汉式
*
* 优点:确保了线程安全,还提高了性能
*/
public class LazySingletonDoubleCheckDemo {
private static LazySingletonDoubleCheckDemo instance;
public static LazySingletonDoubleCheckDemo getInstance() {
if (instance == null) {
synchronized (LazySingletonDoubleCheckDemo.class) {
if (instance == null) {
instance = new LazySingletonDoubleCheckDemo();
}
}
}
return instance;
}
private LazySingletonDoubleCheckDemo() {
System.out.println("instantiation double check lazy singleton");
}
public void doThing() {
System.out.println("this is a double check lazy singleton demo");
}
public static void main(String[] args) {
LazySingletonDoubleCheckDemo.getInstance().doThing();
}
}
使用双重校验锁的线程安全的懒汉式,优点很明显,确保了线程安全,还提高了性能。
但是,这种模式依然不能保证系统的单例模式的实现,在使用反射机制仍然可以创建对象的多个实例。测试代码如下:
/**
* 懒汉式单例设计模式反射加载测试
*/
public class ReflexLoadLazySingletonTest {
public static void main(String[] args) {
// 创建第一个实例
LazySingletonDoubleCheckDemo lazyInstance1 = LazySingletonDoubleCheckDemo.getInstance();
// 通过反射创建第二个实例
LazySingletonDoubleCheckDemo lazyInstance2 = null;
try {
// 使用反射可以创建多个实例
Class clazz = LazySingletonDoubleCheckDemo.class;
Constructor cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);
lazyInstance2 = cons.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// 检查两个实例的hash值
System.out.println("lazyinstance1 hashcode:" + lazyInstance1.hashCode());
System.out.println("lazyinstance2 hashcode:" + lazyInstance2.hashCode());
System.out.println(
"lazyinstance1 equals lazyInstance2:" + (lazyInstance1.hashCode() == lazyInstance2.hashCode()));
}
// 运行结果:
// instantiation double check lazy singleton
// instantiation double check lazy singleton
// lazyinstance1 hashcode:292938459
// lazyinstance2 hashcode:917142466
// lazyinstance1 equals lazyInstance2:false
//
// 结论:
// 通过反射可以创建多个懒汉单例实例,单例结构模式被破坏了
}
从测试代码中我们可以看出,使用Java的反射技术可以创建对象的多个实例,这在我们的实际生产环境中,可能不能满足实际需求的功能实现。
在前面的介绍中,介绍了静态内部类的加载机制,其特点是:加载一个类时,其内部静态类不会同时被加载,并且一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。于是,使用静态内部类的加载机制,提出了另外一种设计饿汉模式。
代码如下:
/**
* 单例模式:使用内部静态类构建懒汉式单例
* 对象实例化是在内部类加载的时候构建的,可见此版本的懒汉单例模式是线程安全的,
* 因为在方法中创建对象,才存在并发问题,静态内部类随着方法调用而被加载,且只加载一次,不存在并发问题,所以是线程安全的。
* 另外,在getInstance()方法中没有使用synchronized关键字,没有加上synchronized从而造成多余的性能损耗。
* 当LazySingletonInnerClassDemo类加载的时候,其静态内部类LazyHolder并没有被加载,因此instance对象并没有构建。
* 而我们在调用LazySingletonInnerClassDemo.getInstance()方法时,内部类LazyHolder被加载,此时单例对象才被构建。
* 因此,这种写法节约内存占用空间,达到懒加载的目的。
*/
public class LazySingletonInnerClassDemo {
/**
* 定义内部静态类
*/
static class LazyHolder {
private static final LazySingletonInnerClassDemo instance = new LazySingletonInnerClassDemo();
static {
System.out.println("Load lazy holder class,className:" + LazyHolder.class.getName());
}
}
public static LazySingletonInnerClassDemo getInstance() {
return LazyHolder.instance;
}
private LazySingletonInnerClassDemo() {
System.out.println("Init lazy singleton inner class,className:" + LazySingletonInnerClassDemo.class.getName()
+ ",hashcode:" + hashCode());
}
public static void main(String[] args) {
// 创建第一个实例
LazySingletonInnerClassDemo instance1 = LazySingletonInnerClassDemo.getInstance();
// 通过反射创建第二个实例
LazySingletonInnerClassDemo instance2 = null;
try {
Class clazz = LazySingletonInnerClassDemo.class;
Constructor cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);
instance2 = cons.newInstance();
} catch (Exception ex) {
ex.printStackTrace();
}
// 检查两个实例的hash值
System.out.println("Instance1 hashcode:" + instance1.hashCode());
System.out.println("Instance2 hashcode:" + instance2.hashCode());
}
// 运行结果:
// Init lazy singleton inner
// class,className:com.singleton.lazyinnerclass.LazySingletonInnerClassDemo,hashcode:292938459
// Load lazy holder
// class,className:com.singleton.lazyinnerclass.LazySingletonInnerClassDemo$LazyHolder
// Init lazy singleton inner
// class,className:com.singleton.lazyinnerclass.LazySingletonInnerClassDemo,hashcode:917142466
// Instance1 hashcode:292938459
// Instance2 hashcode:917142466
//
// 结论:
// 使用反射机制创建实例时,单例结构依然被破坏,无法在系统全局中构建唯一的单例实例
}
这种设计模式,依然没有办法保证整个系统中实现对象唯一的创建一个实例模式。于是提出了对这种设置模式进行了保护作用,从而达到了在系统中创建对象的唯一的一个实例模式。
代码如下:
/**
* 单例模式:使用内部静态类构建懒汉式单例,保护单例使得系统始终只能创建一个单例
*
* 缺点:多次创建实例的时候,程序会抛出异常
*/
public class LazySingletonInnerClassProtectDemo {
private static boolean isInit = false;
/**
* 定义内部静态类
*/
static class LazyHolder {
private static final LazySingletonInnerClassProtectDemo instance = new LazySingletonInnerClassProtectDemo();
static {
System.out.println("Load lazy holder class,className:" + LazyHolder.class.getName());
}
}
public static LazySingletonInnerClassProtectDemo getInstance() {
return LazyHolder.instance;
}
private LazySingletonInnerClassProtectDemo() {
if (isInit == false) {
isInit = true;
} else {
throw new RuntimeException("The singleton has initialize,can't construct again.");
}
System.out.println("Init lazy singleton inner class,className:" + LazySingletonInnerClassDemo.class.getName()
+ ",hashcode:" + hashCode());
}
public static void main(String[] args) {
// 创建第一个实例
LazySingletonInnerClassProtectDemo instance1 = LazySingletonInnerClassProtectDemo.getInstance();
// 通过反射创建第二个实例
LazySingletonInnerClassProtectDemo instance2 = null;
try {
Class clazz = LazySingletonInnerClassProtectDemo.class;
Constructor cons = clazz.getDeclaredConstructor();
cons.setAccessible(true);
instance2 = cons.newInstance();
} catch (Exception ex) {
ex.printStackTrace();
}
// 检查两个实例的hash值
System.out.println("Instance1 hashcode:" + instance1.hashCode());
if (instance2 != null) {
System.out.println("Instance2 hashcode:" + instance2.hashCode());
}
}
// 运行结果:
// Init lazy singleton inner
// class,className:com.singleton.lazyinnerclass.LazySingletonInnerClassDemo,hashcode:292938459
// Load lazy holder
// class,className:com.singleton.lazyinnerclass.LazySingletonInnerClassProtectDemo$LazyHolder
// java.lang.reflect.InvocationTargetException
// at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
// at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
// at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
// at java.lang.reflect.Constructor.newInstance(Unknown Source)
// at
// com.singleton.lazyinnerclass.LazySingletonInnerClassProtectDemo.main(LazySingletonInnerClassProtectDemo.java:41)
// Caused by: java.lang.RuntimeException: The singleton has initialize,can't
// construct again.
// at
// com.singleton.lazyinnerclass.LazySingletonInnerClassProtectDemo.(LazySingletonInnerClassProtectDemo.java:26)
// ... 5 more
// Instance1 hashcode:292938459
//
// 结论
// 使用反射无法破坏单例结构,即阻止了使用反射去创建单例的实例,使得系统全局中始终只有一个单例实例在运行
}
到这里,已经介绍了单例设计模式中常用的设计方法,这基本上满足了日常需求功能开发的要求了。但是,在某些情况下需要在单例类中实现 Serializable 接口,这样就可以在文件系统中存储它的状态。
代码如下:
**
* 单例模式:使用序列化和反序列化创建单例模式
*
* 通过实现 Serializable接口,能保证了系统中始终创建一个单例实例
*/
public class LazySingletonSerializeDemo implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private static boolean isInit = false;
/**
* 定义内部静态类
*/
static class LazyHolder {
private static final LazySingletonSerializeDemo instance = new LazySingletonSerializeDemo();
static {
System.out.println("Load lazy holder class,className:" + LazyHolder.class.getName());
}
}
public static LazySingletonSerializeDemo getInstance() {
return LazyHolder.instance;
}
private LazySingletonSerializeDemo() {
if (isInit == false) {
isInit = true;
} else {
throw new RuntimeException("The singleton has initialize,can't construct again.");
}
System.out.println("Init lazy singleton inner class,className:" + LazySingletonSerializeDemo.class.getName()
+ ",hashcode:" + hashCode());
}
/**
* readResolve()代替了从流中读取对象。这就确保了在序列化和反序列化的过程中没人可以创建新的实例
*/
private Object readResolve() {
return getInstance();
}
public static void main(String[] args) {
// 创建第一个实例
LazySingletonSerializeDemo instance1 = LazySingletonSerializeDemo.getInstance();
ObjectOutput output = null;
ObjectInput input = null;
try {
// 通过序列化创建第二个实例
output = new ObjectOutputStream(new FileOutputStream("LazySingletonSerializeDemo"));
output.writeObject(instance1);
input = new ObjectInputStream(new FileInputStream("LazySingletonSerializeDemo"));
LazySingletonSerializeDemo instance2 = (LazySingletonSerializeDemo) input.readObject();
System.out.println("Instance1 hashCode=" + instance1.hashCode());
System.out.println("Instance2 hashCode=" + instance2.hashCode());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 运行结果:
// Init lazy singleton inner
// class,className:com.singleton.Serializable.LazySingletonSerializeDemo,hashcode:292938459
// Load lazy holder
// class,className:com.singleton.Serializable.LazySingletonSerializeDemo$LazyHolder
// Instance1 hashCode=292938459
// Instance2 hashCode=292938459
//
// 结论:
// 使用了序列化方式能保证了对象的一致性,依然保持了单例结构
}
从测试结果中可以看出,使用序列化和反序列化模式,可是实现保证了在系统全局中实现了对象只能创建一个实例。
本文总结了多种Java中实现单例的方法,其中前两种都不够完美,双重校验锁和静态内部类的方式可以解决大部分问题,平时工作中使用的最多的也是这两种方式。反序列化实现方式虽然很完美的解决了各种问题,但是这种写法让人感觉有些生疏,实际使用中用到的较少。在没有特殊需求的情况下,双重校验锁和静态内部类的方式实现单例模式就已经足够了。