单例对象(Singleton)是一种常用的设计模式,在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在,这样模式的好处在于:
1、某些类的创建比较频繁,对于一些大型的对象,这样的开销是十分巨大的;
2、省去了new操作符,降低了系统内存的使用频率,减轻了garbage collection的压力;
3、有些情况下加入没有使用单例模式,会造成十分严重的后果,例如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了,假如一个军队出现多个司令员,肯定会号令不一,发生乱子。这时候只有使用单例模式,才能保证核心交易引擎服务器独立控制整个流程。
下面创建一个简单的实例对象:
package com.design;
public class Singleton {
private static Singleton instance = null;//静态实例变量,防止被引用,此处赋值为null,实现延迟加载
private Singleton() { //防止被实例化
}
public static Singleton getInstance() { //静态方法创建实例
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public Object readResolve() {
return instance;
}
}
细心的大家可能会发现,这个类没有丝毫线程安全保护机制,在多线程情况下运行,肯定会出问题,
下面改变实例化方法:
public static synchronized Singleton getInstance() { //静态方法创建实例
if (instance == null) {
instance = new Singleton();
}
return instance;
}
使用synchronized关键字锁住的是这个对象,这样锁住了这个对象,就会导致程序的性能出现了下降,因为每次调用这个方法都要使用锁机制,但是实际情况下我们只要在第一次创建对象的时候加锁,之后就不需要了,下面改进方法如下:
public static Singleton getInstance() { // 静态方法创建实例
if (instance == null) {
synchronized (instance) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
上面的改进解决了上面提到的问题,将synchronized关键字加在了内部,在调用的时候是不需要加锁的,这样就在性能上有了一定的提升,但是这也是有很大的问题的,
下面就是一种出现问题的情况:
在Java指令中创建对象和赋值操作是分开执行的,instance=new Singleton()语句是分两步执行的,但是JVM并不保证这两个操作的执行顺序,也就是说JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例,这样可能会出错,下面举出一个出错的例子。
A、B两个独立的线程
(1)A、B线程同时进入第一个if判断
(2)A首先进入synchronized块,由于instance为null,所以执行instance=new Singleton()
(3)由于JVM的内部优化机制,JVM首先划出了一块内存分配给Singleton实例,但是此时没有初始化,并赋值给instance成员,然后A离开synchronized块
(4)B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给该方法的程序
(5)此时B线程打算使用Singleton实例,却发现它还没有初始化,于是产生了错误
实际情况下,我们使用静态内部类来实现单例模式,JVM的内部机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的,当我们第一次调用getInstance的时候,JVM保证instance只被创建一次,并且会保证把值赋给instance的内存初始化完毕,这样我们就不用担心上面的问题,同时该方法只会在第一次调用的时候使用互斥机制,解决了低性能的问题,下面是改正后的全新系统:
public static synchronized Singleton getInstance() { // 静态方法创建实例
return instance;
}
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
下面还有一种实现方法,可以不用内部类实现:这样也可以保证instance初始化完毕
public static Singleton getInstance() { // 静态方法创建实例
if (instance == null) {
syncInit();
}
return instance;
}
private static synchronized void syncInit() {
if (instance == null) {
instance = new Singleton();
}
}
总结:
采用类的静态方法和静态类实现单例效果的不同:
(1)静态类不能实现接口
(2)单例可以被延迟初始化,静态类一般在第一次加载的时候初始化,延迟加载的原因是因为一些类太庞大,延迟加载有助于提升性能
(3)单例类可以被继承,它的方法可以被覆盖,但是静态类内部方法都是static,无法被复写。