单例模式(Singleton)
定义: 单例(Singleton)模式指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
应用场景:只需要一个实例的类
单例模式一共有8中写法,其中只有两种是严格意义上的完美无缺。但实际开发中我们会根据开发需要来使用不同的方法。那么二话不说上代码,分别介绍单例模式的8种实现方式:
1. 饿汉式:
类加载到内存后,就实例化一个单例。
特 点:简单实用,推荐!JVM保证线程安全。
唯一缺点:不管用到与否,类加载时就完成实例化(浪费!)
代码:
public class Singleton01 {
private static final Singleton01 INSTANCE = new Singleton01();
private Singleton01(){}//最关键之处
public static Singleton01 getInstance(){
return INSTANCE;
}
}
首先通过private Singleton01(){}
创建私有化构造方法,别人想要访问只能通过getInstance()
,返回结果永远是INSTANCE的实例。调用方式:
public static void main(String[] args) {
Singleton01 s = Singleton01.getInstance();
}
2. 饿汉式优化:
此方式对上一种方式做了简单规范优化,将
INSTANCE
放入静态块中初始化,与上一种本质上并无区别。
代码:
public class Singleton01 {
private static final Singleton01 INSTANCE;
static {
INSTANCE = new Singleton01();
}
private Singleton01(){}
public static Singleton01 getInstance(){
return INSTANCE;
}
}
3. 懒汉式(lazy loading)
优 点:达到按需初始化的目的
缺 点:线程不安全
public class Singleton03 {
private static volatile Singleton03 INSTANCE;
private Singleton03(){}
public static Singleton03 getInstance(){
if (INSTANCE == null) {
INSTANCE = new Singleton03();
}
return INSTANCE;
}
}
在需要调用getInstance()
方法时对INSTANCE
进行初始化,但必须加上volatile
,否则在多线程访问下可能出现实例不唯一的情况。
4. 加锁(synchronized)
此方式仅在"public staticSingleton04 getInstance() {}"这句上加了
synchronized
,其余代码与上条无异。
优 点:线程安全
缺 点:占用内存量大,效率低
代码(与上一条不同处):
public static synchronized Singleton04 getInstance()
通过对getInstance()
加锁就可以解决上面说的线程安全问题。但同时因为加的是静态锁,使内存使用量大,效率降低。
5. 优化锁
此方法将锁放入方法中,通过判断
INSTANCE
未被加载时加锁进行初始化。
代码(与上一条不同处):
public static Singleton05 getInstance(){
if (INSTANCE == null) {
synchronized (Singleton05.class) {
INSTANCE = new Singleton05();
}
}
return INSTANCE;
}
通过减少同步代码块的方式提高效率,但为解决多线程访问会出现实例不唯一的情况。
6. 双重判断
根据之前遇到的问题进行总结,对加锁后的单例进行再次判断线程安全后进行初始化。
优 点:完美的解决了以上的所有问题。
缺 点:没有。(除非你觉得写得比较复杂)
代码(与上一条不同处):
public static Singleton06 getInstance(){
if (INSTANCE == null) {
synchronized (Singleton06.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton06();
}
}
}
return INSTANCE;
}
此方法是单例模式中的最完美的写法之一,并在此基础上衍生了以下两种写法。
7. 静态内部类
在第一种方式的基础上改用静态内部类方式
优 点:JVM保证线程安全同时实现懒加载
代码:
public class Singleton07 {
private Singleton07(){}
private static class S07Holder {
private final static Singleton07 INSTANCE = new Singleton07();
}
public static Singleton07 getInstance(){
return S07Holder.INSTANCE;
}
}
此方法也是单例模式种最完美的实现方式之一,使用静态内部类加载外部类时不会加载内部类,实现了懒加载。
8. 枚举单例
Java的创始人之一Joshua Bloch曾写过一本书《Effective Java》,其中他使用枚举(enum)来实现单例模式。
代码:(仅此而已。。)
public enum Singleton08 {
INSTANCE;
}
完美中的完美(大神就是大神)。。通过枚举类型解决了以上出现的所有问题。使用时直接通过Singleton08.INSTANCE
调用。
关于JVM为什么能保证线程安全请参考我的文章:Java复习之-JVM概述、JVM学习之——类加载。
单例模式的优点和缺点
单例模式的优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
单例模式看起来非常简单,实现起来也非常简单。单例模式在面试中是一个高频面试题。希望大家能够认真学习,掌握单例模式,提升核心竞争力,给面试加分,顺利拿到 Offer。