什么是单例模式?
单例模式 是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例,并提供一个访问它的全局访问点。
单例模式的用途?
用于创建只需要一个实例的场景。
怎么实现单例模式?
单例模式的实现中有几个共同特点:
- 使用 private 声明类构造函数,这样就不能通过new 声明这个类的对象了,防止形成多个实例。
- 使用 private 声明类的单实例,防止外部直接访问。
- 提供唯一的全局访问方法,如getSignleton()来访问单实例。
单例模式性能的评价标准:
- 延迟加载
- 线程安全
- 并发访问性能
- 内存可见性
- 使用通用性
- 序列化不破坏单实例
Java中实现单例模式有多种方法,下面我们一一介绍:
- 饿汉式
public class Singleton {private static Singleton singleton = new Singleton();private Singleton() {}public static Singleton getSignleton(){return singleton;}}
顾名思义,这个实现就像是饥不择食的饿汉,在类装载时就忍不住实例化了。
非延迟加载:这种方式单实例
singleton
在类装载时就实例化,由于在静态成员singleton 使用了new
会在类加载时就初始化,这时候初始化
singleton
没有延迟加载的效果,如果这个单例类没有被用到,那么就会造成资源浪费。
线程安全:
类装载时实例化,一个类只会有同一类加载器加载一次,由JVM保证线程安全。
详细解释请看:
【单例深思】饿汉式与类加载
2. 懒汉式
public
class
Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
为了解决饿汉式的延迟加载问题,可以使用懒汉式实现,只有在需要使用的时候才实例化,能拖则托。
但是不能保证线程安全。
延迟加载:这种方式单实例
singleton
第一次被引用的时候初始化。
非线程安全:在多线程情况下,如果
singleton
还没有被创建,此时有可能多个线程同时进入
getInstance()
方法中,这时if 判断时
singleton
在都为空,则可能会创建多个实例,与单实例的要求不符,因此不是线程安全的。
3.懒汉式改进版
public
class
Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
为了使得懒汉式变成线程安全的,可以使用内置锁来保证
线程安全。
但是这样却引入了并发性能问题。
延迟加载:这种方式单实例
singleton
第一次被引用的时候创建。
线程安全:
使用
synchronized
给
getInstance()
加锁,这样一次只能有一个线程进入
getInstance()
方法中,也就能满足线程安全了。
并发访问性能:
单实例只会创建一次,所以
getInstance()
中的
if
判断只有第一次请求的时候为true,非第一次请求都会直接返回单实例。
synchronized
给
getInstance()
整个方法加锁
,方法内
所有的操作都会同步进行的,但是除了第一次创建单实例情况外,根本不需要同步操作,因此如果这个单实例访问频繁,会因为使用同步而影响使用性能。
详细解释请看:
【单例深思】懒汉式改进版与内置锁
4.双重检测锁
public
class
Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
为了解决
懒汉式改进版
中
存在的性能问题,我们使用同步代码块缩小锁的范围,以提高并发量。
两次检测
singleton
是否为空,并使用锁同步实例化代码,因此得名双重检测锁。
但是,这种写法还是
可能
会出现可见性问题。
延迟加载:这种方式单实例
singleton
第一次被引用的时候创建。
线程安全:
使用
synchronized
给创建单实例的代码枷锁,形成同步代码块,保证只创建一次
。
并发访问性能:
只有第一次请求单实例时会进入同步代码块创建单实例,除此之外的其他请求不会进入到同步代码块中,
getInstance()
在除第一次访问外相当于没有加锁,也不会造成性能问题。
内存可见性:
JVM内存模型有两个重要的概念,叫做指令重排序和内存可见性
,
getSingleton()
方法可能会出现这种情况,有多个线程进入了该方法,有一个线程获得了锁进行单实例创建,其他线程直接返回,但是返回的对象却未正常实例化,造成使用时出错。
5.双重检测锁改进版
public
class
Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
为了解决
双重检测锁中出现的问题,需要使用
volatile
声明
singleton
,以保证单实例创建成功。
但是,这种方法不是通用的。
延迟加载:这种方式单实例
singleton
第一次被引用的时候创建。
线程安全:
使用
synchronized
给创建单实例的代码枷锁,形成同步代码块,保证只创建一次
。
并发访问性能:
只有第一次请求单实例时会进入同步代码块创建单实例,除此之外的其他请求不会进入到同步代码块中,
getInstance()
在除第一次访问外相当于没有加锁,也不会造成性能问题。
可见性:
使用
volatile
声明
singleton
,
volatile
可以保证内存可见性并告诉编译器不进行指令重排序,当
有多个线程进入了
getSingleton()
方法,有一个线程获得了锁进行单实例创建,其他线程直接返回,这时只要单实例完成创建,其他线程就可以立即获得该实例,并且编译器不会进行指令重排,程序会按我们写的代码顺序执行。
通用性:
volatile
在
Java 1.5中才发挥上述作用,因此该方法只能在
Java 1.5及
以上才可使用。
6.静态内部类式
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
延迟加载:
SingletonHolder
为静态内部类,
Singleton
初始化时不会初始化
SingletonHolder
,只有
通过
显示调用
getInstance()
方法时,才会初始化
SingletonHolder
类,从而实例化单实例,满足延迟加载
。
线程安全:单实例在类加载过程中创建,JVM 保证线程安全
。
并发访问性能:
没有加锁,不会造成并发性能问题。
可见性:
安全,没有先判断再创建的模式
。
通用性:
通用,没有使用
volatile
。
7.枚举实现
public
enum
Singleton {
INSTANCE;
private Singleton() {}
}
实际工作使用很少。
延迟加载:
满足延迟加载
。
线程安全:
创建枚举默认就是线程安全的。
并发访问性能:
没有加锁,不会造成并发性能问题。
可见性:
安全,没有先判断再创建的模式
。
通用性:1.5之前不可用
。
序列化问题:
无。
8. 序列化安全
public
class
Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return singleton;
}
}
一旦单例实现了序列化接口,那么它可能就不是序列化安全的了,存了枚举实现外,其他方式都存在序列化问题,只要在
Singleton
类中定义
readResolve
()
就可以解决该问题。
总结
实现单例的方法很多,个人推荐静态内部类的方式,功能完备,使用简单,易于理解。