单例模式是面试中频繁出现的设计模式,也是多种设计模式中比较容易理解的一个设计模式,那么什么是单例模式?就是保证一个类仅有一个实例,并提供一个访问它的全局访问点。使用单例模式的原因在于,在某些场景下,对一些类来说,只有一个实例是很重要的。
举个例子来说,对于常用的windows系统来说,文件管理器是一个必不可少的组件,然而不同的进程中可能会同时对同一个文件进行操作,试想一下,如果是多个文件处理器去操作的话,这其中会产生很多的同步问题而且很难处理,那么如果只有一个文件管理器的实例,每次操作都是同一个实例进行处理,就可以避免混乱的现象。因此,一般来说,单例模式最大的好处就在于节省了资源,并且有利于并发资源的使用。
那我们怎么样才能保证一个类只有一个实例并且这个实例易于被访问呢?一个全局变量使得一个对象可以被访问,但它不能防止你实例化多个对象。一个更好的办法是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建(通过截取创建新对象的请求),并且它可以提供一个访问该实例的方法。这就是Singleton模式。
接下来,我们好好分析一下单例模式的具体实现:
1.最简单的实现:
public class SingletonClass {
private static final SingletonClass instance = new SingletonClass();
private SingletonClass() {
...;
}
public static SingletonClass getInstance() {
return instance;
}
}
这里的细节在于,使用private来保证不会有多余的实例产生,使用static final来修饰instance 保证这个唯一实例不会被改变,并且可以被调用,但是缺点在于,不论这个实例是否需要被创建,第一次调用这个类的时候,这个实例一定会被创建,也因此,这种方法被称作饿汉法。
2.懒汉法的单线程写法:
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton() {
if(singleton == null) singleton = new Singleton();
return singleton;
}
}
相比于上一种写法,可以很明显看出,在工厂方法中,进行了一个if判断,如果空才会初始化创建一个新的出来,也就是所谓的懒汉式。但是这种写法只是适用于单线程的情况,假设有两个线程同时调用singleton,并且初始判断都是null,那么会造成new出来两个singleton的情况。
2.懒汉法的多线程写法:
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
return singleton;
}
}
首先需要插一句题外话,这里对static方法中synchronized (Singleton.class)属于Java里加锁的两种方式之一,Java的锁synchronized一种加在实例上成为实例锁,一种加在类上,称为类锁,这两种锁事实上一般是互相独立的,意思是说:同一个实例不能有两个线程获得自己的实例锁,同一个类不会有两个线程获得自己的类锁,但是有可能存在,两个线程分别获得了一个类锁和一个实例锁。
这里很明显是使用的类锁,因为是在static方法中Singleton.class上锁,那么看起来上是满足了线程安全性的,只是效率实在是极其低下,因为大多数情况下,线程只是想读取而不是创建,这样的话,每次读取都要排队取锁,实在是效率太低。
那么优化后的写法如下:
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
很明显,先做一步if判断的就是为了大多数情况下已经存在这个实例时,线程可以直接进行读取,不需要再进行排队取锁,这种就是所谓的双重检验锁写法。
3.静态内部类写法:public class Singleton {
private static class Holder {
private static Singleton singleton = new Singleton();
}
private Singleton(){}
public static Singleton getSingleton(){
return Holder.singleton;
}
}
这种事实上是我个人觉得最容易理解也最省事的写法,利用静态内部类的加载机制,既不会产生饿汉写法那种不调用就初始化,也没有什么线程安全的问题。
4.还有一种很炫技的写法,就是枚举,但是这种我自己理解也不深刻,这里仅仅把代码放出。
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
综上!!!
public enum Singleton {
INSTANCE;
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}