说说Java中的单例模式



单例模式算是最简单最容易理解的设计模式,在项目中会经常使用。写法呢,很多种,最好的或者最适合的是哪一个?

设计一个单例要考虑哪些因素呢:

  1. 单例
  2. 延迟创建或加载
  3. 线程安全

写法1

public class Singleton {
    private static Singleton ourInstance = new Singleton();

    public static Singleton getInstance() {
        return ourInstance;
    }

    private Singleton() {
    }
}

这个被称作饿汉式,用空间换时间,类一加载就创建单例的实例,用的时候直接返回就行了。但是这样会过早的创建实例,从而降低内存的使用效率。

写法2

public class Singleton {
    private static Singleton ourInstance ;

    public static Singleton getInstance() {
        if (ourInstance == null){
            ourInstance = new Singleton();
        }
        return ourInstance;
    }

    private Singleton() {
    }
}

这个是用时间换空间,叫懒汉式,实际用到的时候再去创建实例。 如果你的程序不牵扯到多线程,那么这个可以了。 但这个写法在多线程的情况下就有问题了,如果同时有两个线程运行到判断ourInstance是不是null的if语句,而且ourInstance的确没有创建时,那么两个线程就会创建一个实例,那么现在就不能保证单例模式的要求了。

写法3

加锁

public class Singleton {
    private static Singleton ourInstance ;

    public static Singleton getInstance() {
        if (ourInstance == null){
            synchronized (Singleton.class){
                if (ourInstance == null) {
                    ourInstance = new Singleton();
                }
            }
        }
        return ourInstance;
    }

    private Singleton() {
    }
}

在创建实例前加同步及判断,并且使用两个if判断来保证效率,保证了在多线程下也能够保证会有一个实例了。

写法4

写法3的代码感觉比较复杂

public class Singleton {

    public static Singleton getInstance() {
        return Nested.singleton;
    }

    private Singleton() {
    }

    static class Nested{
        static Singleton singleton = new Singleton();
    }
}

在内部定义了一个私有内部类,类型Nested只有在Nested.singleton中被用到。因此当我们第一次试图通过getInstance()去取实例的时候,Nested才会加载并实例化一个Singleton实例。

写法5

使用原子操作的方式

public class Singleton {

    private static AtomicReference<Singleton> instance = new AtomicReference<>(null);

    public static Singleton getInstance() {
        if (instance.get() == null){
            instance.compareAndSet(null, new Singleton());
        }
        return instance.get();
    }
    private Singleton() {
    }
}

其中 compareAndSet(V expect, V update): 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

这种方式既能够保证延迟加载又能保证原子性及实例的唯一性,代码也相对比较简洁。