Java单例模式的实现
为什么要用单例模式?
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。
- 由于new操作的次数减少,所以系统内存的使用评率也会降低,这将减少GC压力,缩短GC停顿时间。
单例模式的几种形式
懒汉式
public class Singleton {
//初次声明,什么也不做,只声明一个引用指向null
private static Singleton singleton=null;
private Singleton() {
}
//等到外部需要get的时候再创建实例对象
public static Singleton getInstance(){
if (singleton== null) {
singleton = new Singleton();
}
return singleton;
}
}
饿汉式
public class Singleton {
//一开始声明就new出来对象实例
private static Singleton singleton= new Singleton();
private Singleton() {
}
public static Singleton getInstance(){
return singleton;
}
}
上述两种形式,从名称就可以看出具体的含义:
- 懒汉式,很懒,所以刚开始什么也不干,等到非干不可时,再临时new一个
- 饿汉式,很饿,刚开始就给他new一个准备好,用的时候不用等,直接“吃”
但是上述两种方法只在单线程中适用,一旦到了多线程场景,就无法保证线程安全,所以需要对线程进行加锁,加锁也可分两种:
- 一种是在整个getInstance函数上加锁
- 一种是仅仅在同步块上加锁
两种加锁都是相对于懒汉式而言的,但一般来说在整个函数上加锁性能过低,所以采用在同步块上加锁
下面是具体实现:
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance(){
if (singleton== null) {
//对这一块全部加锁
synchronized (Singleton.class) {
if (singleton == null) {
singleton= new Singleton();
}
}
}
return singleton;
}
}
但是这种实现依然有问题,由于JVM编译时的指令重排序问题,依然会造成singleton对象在多线程场景下的不一致,所以需要用volatile关键字修饰:
双重锁检测机制
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance(){
if (singleton== null) {
//对这一块全部加锁
synchronized (Singleton.class) {
if (singleton == null) {
singleton= new Singleton();
}
}
}
return singleton;
}
}
- 用
volatile
修饰singleton
是为了保持可见性,并防止指令重排序 - 用
private
修饰 构造函数 是为了防止其被实例化 - 采用双锁检测机制保证了单例模式的线程安全
- 最终在类内采用静态方法,把访问对象的唯一接口暴露出去