1.概述
单例模式的定义:一个类只能创建一个实例
为什么使用单例模式?
- 频繁创建对象、管理对象是一件很耗费资源的事,如果只需要一个实例来使用可以节省资源
- 表示全局唯一类:在业务概念上,如果有些数据在系统中只保存一份,那么就适合设计成单例模式
- 例如:配置文件、唯一递增ID号码生成器
2.创建
创建单例模式其实很简单,分为三步:
- 将构造函数私有化
- 在类的内部创建实例
- 提供获取唯一实例的方法
1.饿汉式单例
饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。
缺点:
- 这样的实现方式不支持延迟加载实例。
- 一开始就创建实例,一旦这个类没有被使用,那么就造成了资源浪费!
/**
* 饿汉式单例
*/
public class EagerSingleton {
//1-将构造函数私有化
private EagerSingleton(){};
//2-在类的内部创建实例
private static EagerSingleton eagerSingleton = new EagerSingleton();
//3-提供获取唯一实例的方法
public static EagerSingleton getInstance(){
return eagerSingleton;
}
}
2.懒汉式单例
由于懒汉式单例存在问题:无法延迟加载、可能造成内存浪费
所以引出了饿汉式单例=>只有在使用时才会创建【第一次使用时】
缺点:
饿汉式单例缺点非常明显,在多线程情况下如果不加锁==>可能会创建多个实例
**加锁!**===>影响并发效率!!【每次获取实例都会被锁(不管是否已创建)】
/**
* 懒汉式单例
*/
public class LazySingleton {
private LazySingleton(){};
//初始化为null
private static LazySingleton instance = null;
//当使用该类时才会去创建
//有线程安全问题==>需要加锁
public static synchronized LazySingleton getInstance() {
if(instance==null){
instance = new LazySingleton();
}
return instance;
}
}
3.双重检测DLC
由于懒汉式单例存在性能问题,所以引入双重检测的单例模式!
- 首先,将锁的范围缩小==>只有通过第一层检测后才会竞争类锁
- 其次,获取到锁后再做一次检测【同时只有一个线程获取】,之后创建实例【保证了只有一个实例】
/**
* 双重检测单例
*/
public class DoubleChecked {
private DoubleChecked(){};
private static volatile DoubleChecked instance = null;
public static DoubleChecked getInstance(){
if(instance==null){ //第一层检测
synchronized (DoubleChecked.class){ //类锁
if(instance==null){//第二层检测
instance = new DoubleChecked();
}
}
}
return instance;
}
}
当然,这个方法还可能会有指令重排的问题 ==> 由于
instance = new DoubleChecked();
不是原子操作。当他在实例化时【还未初始化】 此时其他线程来get,它判断不为null,但是获取到的是没有初始化的实例!
要解决也十分简单,加上我们的volatile关键字就可以了,volatile有内存屏障的功能
4.静态内部类
一种比双重检测更加简单的实现方法,那就是利用 Java 的静态内部类。
它有点类似饿汉式,但又能做到了延迟加载。
public class InnerSingleton {
private InnerSingleton(){}
//静态内部类不会在外部类加载时被加载
private static class SingletonHolder{
private static final InnerSingleton instance = new InnerSingleton();
}
public static InnerSingleton getInstance(){
return SingletonHolder.instance;
}
}
SingletonHolder
是一个静态内部类,当外部类 InnerSingleton
被加载的时候,并不会创建
SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,
这个时候才会创建 instance。insance 的唯一性、创建过程的线程安全性,都由JVM 来保证。
所以,这种实现方法既保证了线程安全,又能做到延迟加载。
5.枚举实现
public enum EnumSingleton{
INSTANCE;
}
这种实现方式通过Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。
**