Java单例模式的实现(饿汉式、懒汉式、双重锁检测机制)

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修饰 构造函数 是为了防止其被实例化
  • 采用双锁检测机制保证了单例模式的线程安全
  • 最终在类内采用静态方法,把访问对象的唯一接口暴露出去

你可能感兴趣的:(Java设计模式)