一、介绍
什么是单例模式?
通俗的讲,就是在应用程序中只需要某个类保留唯一一个实例对象,不希望有更多的实例。单例模式是java设计模式中最简单的设计模式之一,在应用程序中经常被用到。
二、应用场景
单例模式的应用场景有很多,比如线程池、日志对象、缓存、数据库连接池、计算机系统设备管理器等等。这些常常都设计成全局唯一的,方便集中管理,也节省系统的开销。
三、实现方式
实现单例模式要注意以下三点:
1、单例类只能有一个实例,不能从其他对象中new出来, 即构造器用private修饰。
2、单例类必须自己创建自己的唯一实例,需要实现一个方法提供这个实例。
3、单例类必须能给其他对象提供这一实例。
接下来我们讲讲在Java中如何实现单例模式 :
(1)饿汉式
饿汉式,顾名思义指的是在类加载的时候就初始化好对象,不管有没有用到。
public class Singleton {
private final static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
public void doSomething(){
// 业务方法
}
}
(2)懒汉式
懒汉式和饿汉式相对,指的在程序加载时不初始化对象,什么时候被引用什么时候才初始化对象,即在第一次使用的时候才去初始化对象,可以避免内存浪费。注意在获取实例的getInstance()方法前加上了synchronized关键字,这是为了保证线程安全,避免多线程同一时刻获取对象时造成生成了多个实例。
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public synchronized static Singleton getInstance(){
if(singleton == null){ // 1
singleton = new Singleton(); // 2
}
return singleton;
}
public void doSomething(){
// 业务方法
}
}
(3)双重检查锁
双重检查锁是在懒汉式基础上演变过来的,当分析懒汉式代码时,你会发现只有在第一次调用获取实例方法时才需要同步。因为仅//2处的代码需要同步,但只有第一次调用才执行此行,后面的其他调用没有执行此行,但都付出了同步的代价。为了提高效率,双重检查锁应运而生,为什么要二次检查,分析双重检查锁代码,当第一次获取实例多个线程并发到达//2处, 第一个线程执行完synchronized的代码块后,后面的线程仍然需要对singleton 进行第二次检查,所以需要对实例对象做两次检查。java内存模型允许无序写入,//4行代码构造器执行之前,变量singleton可能成为非null的,所以singleton需要加上volatile关键字。
public class Singleton {
private volatile static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance(){
if(singleton == null){ // 1
synchronized (Singleton.class){ // 2
if(singleton == null){ // 3
singleton = new Singleton(); // 4
}
}
}
return singleton;
}
public void doSomething(){
// 业务方法
}
}
(4)静态内部类
这种方式能达到双检锁方式一样的功效,但实现更为简单。这种和饿汉式比较,在类加载时,singleton实例并没有被初始化,需要显示调用getInstance()方法才会转载SingleHolder类,从而初始化singleton实例,所以达到了延时加载的效果。此方法在实际使用中用的最多,推荐此种写法。
public class Singleton {
private static class SingleHolder{
private static Singleton singleton = new Singleton();
}
private Singleton(){ }
public static Singleton getInstance(){
return SingleHolder.singleton;
}
public void doSomething(){
// 业务方法
}
}
(5)枚举
这种方式巧妙的应用了枚举的特点,构造器本身私有,写法简单,自动支持序列化机制,防止多次实例化,获取实例可以通过Singleton.INSTANCE来访问。
public enum Singleton {
INSTANCE;
public void doSomething(){
// 业务方法
}
}
四、比较
方式 | 是否支持延时初始化 | 实现难度 | 线程安全 |
---|---|---|---|
饿汉式 | 不支持 | 简单 | 安全 |
懒汉式 | 支持 | 简单 | 安全 |
双重检查锁 | 支持 | 复杂 | 安全 |
静态内部类 | 支持 | 一般 | 安全 |
枚举 | 不支持 | 简单 | 安全 |