单例模式是很常见的设计模式,当某个类在整个系统中只需要存在一个对象时,比如说一个管理整个服务器配置的类,它的实例在系统中只能存在一个,这个时候就需要使用单例模式来确保这个类不会被实例化第二次。单例模式是Java所有设计模式中类图最简单的模式,但这并不代表单例模式就很简单。
单例模式一般有懒汉式、饿汉式、双重检查、静态内部类和枚举等几种实现方式,实现方式各不相同,但一定有一点是相同的,这些实现方式首先都得把类的构造方法改为私有,这样才能防止直接被实例化。下面就来列举下这几种实现方式:
1、懒汉式(线程不安全)
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
所谓懒汉模式,顾名思义就是类比较懒,不通过调用getInstance()方法不会主动生成自己的实例。但是这个实现方式是线程不安全的,如果代码在多线程环境中运行,很可能会发生多个线程同时调用getInstance()方法从而导致实例化多次,多线程环境下不可使用。
2、懒汉式(线程安全,同步方法)
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
第二种方法在第一种的基础上对getInstance()方法加了个线程同步,这样子就是线程安全的实现方法了。这种方法虽然实现了线程安全,但是线程同步是加在getInstance()方法上的,每个线程想要获得实例时执行getInstance()方法都要进行同步,同步效率比较低,不推荐使用。
3、懒汉式(线程安全,同步代码块)
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
}
这种实现方式采用同步代码块的方式来代替同步方法的方式,效率的确变高了,但是这种实现方式和第一种一样并不能起到线程同步的作用。如果在线程A通过if(singleton == null)判断语句块开始执行同步代码块的时候,线程B也通过判断语句等待执行同步代码块。这样当线程A执行完毕同步代码块并释放的时候,Singleton类的实例实际上已经创建出来了,可是线程B已经通过判断语句进来了,所以类Singleton会被再次实例化,
多线程环境下不可使用。
4、双重检查(线程安全,同步代码块)
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的双重检查锁定方式很好的实现了单例模式,第一次检查时没有锁定,看看这个域是否被初始化了;第二次检查时有锁定。只有当第二次检查时表明这个域没有被初始化,才会生成类的实例。
因为如果域已经被初始化就不会有锁定,域被声明为volatile很重要!
5、饿汉式(静态常量)
public class Singleton {
private final static Singleton SINGLETON = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return SINGLETON;
}
}
这种实现方式之所以叫做“饿汉式”,是因为不管getInstance()方法是否有被调用,它都会在类装载的时候完成实例化。但是这样就不能够实现延迟加载,如果这个类的实例比较大,那么实例所占的内存在需要被实例化前就消耗掉了,比较浪费内存。
6
、
饿汉式(静态代码块)
public class Singleton {
private final static Singleton singleton;
static{
singleton = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
这种实现方式和上面的实现方式类似,只不过将实例化的过程放在了静态代码块中,同样也是在类装载的时候完成了实例化。
7
、
静态内部类
public class Singleton {
private Singleton(){}
private static class Instance{
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return Instance.singleton;
}
}
这种实现方式和“饿汉式”实现原理类似,都是利用JVM的类装载机制来防止多次实例化。但是静态内部类的实现方式不会在类装载的时候就直接实例化,而是在调用getInstance()方法时才会装载内部类Instance,从而完成实例化。
8、枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。