单例模式

 一、单例模式(Singleton Pattern)的定义
  1、单例模式 就是被单例的对象只能有一个实例存在。
  2、单例模式的实现方法是:一个类能返回对象的一个引用(永远是同一个)和一个获得该唯一实例的方法(必须是静态方法)
  3、通过单例模式,我们可以保证系统中只有一个实例,从而在某些特定的场合下达到节约或者控制系统资源的目的。
 
二、单例模式类图
      在【装饰者模式】中,一个人拥有各种不同特征的女朋友,但最后只能拥有一个老婆。
单例模式单例模式

三、示例代码
1、饿汉模式
    饿汉模式就是很饥渴,所以一上来就需要给它创建一个实例。
    缺点:不管有没有调用过获得实例的方法(getWife()),每次都会新建一个实例。
// 饿汉模式
public class Wife {
// 一开始就新建一个实例
private static final Wife wife = new Wife();
// 默认构造方法
private Wife() {}
// 获得实例的方法
public static Wife getWife() {
     return wife;
}
}

2、懒汉模式
     懒汉模式:一开始不新建实例,只有当它需要使用的时候,会先判断实例是否为空,如果为空才会新建一个实例来使用。
// 懒汉模式
public class Wife {
//一开始没有新建实例
private static Wife wife;
private Wife() { }
// 需要时再新建
public static Wife getWife() {
if (wife == null) {
wife = new Wife();
}
return wife;
}
}

3、线程安全的懒汉模式
     懒汉模式的缺点:如果有多个线程并行调用getWife()时,还是会创建多个实例,单例模式就失效了。
——改进:设为线程同步(synchronized)
——synchronized的作用:保证在统一时刻最多只有一个线程运行,避免多线程带来的问题。
// 懒汉模式(线程安全)
public class Wife {
private static Wife wife;
private Wife() { }
// 添加了 synchronized 关键字
public static synchronized Wife getWife() {
if (wife == null) {
wife = new Wife();
}
return wife;
}
}

4、双重检验锁double check
     线程安全的懒汉模式:解决了多线程问题,但是效率不高(每次调用获得实例方法getWife()时要进行同步,但多数情况下并不需要同步操作。)
——改进:当wife实例不为空,则可以直接使用,就不需要给getWife()加同步方法,直接返回wife实例就可以了。

// 双重锁的 getWife() 方法
public static Wife getWife() {
// 第一个检验锁,如果不为空直接返回实例对象,为空才进入下一步
if (wife == null) {
     synchronized (Wife.class)
{ //第二个检验锁,因为可能有多个线程进入到 if 语句内
      if (wife == null) { wife = new Wife(); } }
}
return wife ;
}

——存在的问题:因为在JVM指向这句代码的时候,要做好几件事情,而JVM为了简化代码,有可能造成这几件事情的执行顺序是不固定的,从而造成错误。
——改进:给实例加一个volatile关键字(作用:防止编译器自行优化代码)

// 双重检验锁
public class Wife {
     private volatile static Wife wife;
     private Wife() { }
     public static Wife getWife() {
          if (wife == null) {
               synchronized(Wife.class) {
                    if (wife == null) {
                         wife = new Wife();
                    }
                }
           }
          return wife;
      }
}

5、静态内部类
     静态内部类利用JVM自身的机制来保证线程安全,因为WifeHolder类是私有的,除了getWife()之外没有其他方式可以访问实例对象,而且只有在调用getWife()时才会去真正创建实例对象。

// 静态内部类
public class Wife {
private static class WifeHolder {
     private static final Wife wife = new Wife();
}
private Wife() { }
public static Wife getWife() {
     return WifeHolder.wife;
}
}

6、枚举
// 枚举
public enum Wife {
INSTANCE;
// 自定义的其他任意方法
public void whateverMethod() { }
}
可以通过Wife.INSTANCE来访问实例对象,比getWife()简单,而且创建枚举默认就是线程安全的,还可以防止反序列化带来的问题。

参考: