单例模式是java中最简单的设计模式之一,他有几种要求
- 单例类里面只允许有一个单例
- 单例类必须自己创建自己的单例,即需要让构造器私有化
- 单例类必须可以给其他对象提供自己的实例
我们要知道设计模式都是围绕着七种设计原则:单一职责原则,接口隔离原则,里式替换原则,开闭原则,依赖倒置原则,迪米特法则(demeter)以及合成复用原则。
下面谈一谈单例模式的几种实现方法:
饿汉模式
饿汉模式是很常见的一种模式,比如jdk的runtime类就是用的饿汉模式,他的思想是在最开始
便创建好自己的实例化对象,谁来取,就直接给谁用,这种方式是绝对线程安全的,实现的
代码也很简单:
public class Hungry {
private static final Hungry instance = new Hungry();
private Hungry(){}
public static Hungry getInstance(){
return instance;
}
}
当然,饿汉模式不只这一种写法,还可以把对象的实例化交给静态代码块,因为静态代码块在类加载的时候是要先加载的,所以符合饿汉模式的设计要求,代码如下:
public class Hungry2 {
private static final Hungry2 instance;
//跟第一种类似,只不过是把对象的实例化放在了静态代码块里面
static {
instance = new Hungry2();
}
private Hungry2() {
}
public static Hungry2 getInstance() {
return instance;
}
}
但是饿汉模式可能会造成资源的浪费,因为你有可能从始至终都没有用到我的实例对象,所以有第二种模式。
懒汉模式
懒汉模式的设计思想是什么时候用,我什么时候再给你创建,即懒加载机制(lazy-loading),这样可以保证我们创建的对象一定会被用到,但是这种机制会导致线程的不安全,我们先看代码:
public class LazySingleTon {
private LazySingleTon(){}
private static LazySingleTon instance;
public static LazySingleTon getInstance(){
//当对象为空的时候才去实例化,但是有可能多个线程同时进入if语句,创建出多个对象
if(instance == null){
instance = new LazySingleTon();
}
return instance;
}
}
缺点很明显,在多线程同时调用getInstance()的时候,可能都进入了if判断,但是第一个进来的线程还没来得及创建对象,所以可能会创建多个对象。
那么要实现线程安全,最简单的方式就是加锁:
public class LazySingleTon2 {
private LazySingleTon2(){}
private static LazySingleTon2 instance;
public static synchronized LazySingleTon2 getInstance(){
//相对于懒汉模式,方法加入了synchronized来修饰,保证线程安全,但是效率会大大降低
if(instance == null){
instance = new LazySingleTon2();
}
return instance;
}
}
加入同步锁之后可以保证同一时间只能有一个线程调用getInstance()方法,保证了线程安全,但缺点也很明显——太慢了!于是有人又想出了一种方法:不用同步锁修饰方法,而是修饰一个代码快:
public static LazySingleTon2 getInstance() {
if (instance == null) {
synchronized (LazySingleTon.class) {
instance = new LazySingleTon2();
}
}
return instance;
}
乍一看感觉好强,但是仔细一想其实没卵用,因为这种方式根本就不能阻止线程去new对象,线程只是进入if语句之后被阻塞了,哪怕它等待了一万年,在它开始执行的时候还是会去创建对象。
但是在这个代码的基础上再稍微修改一点就可以用了:
public class DoubleCheck {
private static volatile DoubleCheck instance;
private DoubleCheck() {
}
public static DoubleCheck getInstance() {
if (instance == null) {
synchronized (DoubleCheck.class) {
if (instance == null) {
instance = new DoubleCheck();
}
}
}
return instance;
}
}
意思就是在代码块里面再加一个判断,如果别的线程已经创建了对象,后面等待的所有线程都不会再去实例化对象,这种方式叫做 双重检查锁 单例模式(懒汉模式,线程安全)
静态内部类
静态内部类在类加载的时候并不会初始化,而是在调用的时候才会,这很符合懒加载机制,而且静态内部类初始化的时候是绝对线程安全的。所以这种方式推荐大家使用。
public class StaticInnerClass {
//类加载时,静态内部类不会加载,当调用的时候才会初始化
private static class singleTonHolder{
private static final StaticInnerClass instance = new StaticInnerClass();
}
//构造器私有化,防止new对象
private StaticInnerClass(){}
public static StaticInnerClass getInstance(){
return singleTonHolder.instance;
}
}
枚举类
最后一种,枚举类,枚举类型我们平时很少用到,但是用它来实现单例模式很简单,先看代码:
public enum EnumSingle {
ENUM_SINGLE;
public static EnumSingle getInstance(){
return ENUM_SINGLE;
}
}
枚举类的会自动对常量添加 public static final修饰,并且enum类本身也是final的,而用枚举实现单例模式有三个特性:自由序列化,线程安全,保证单例。
enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。