一、介绍
采取一定的方法,让软件系统一个类只能创建和使用一个实例对象,并提供一个取得对象的方法
二、作用
单例模式保证系统中这个类只有一个对象,节省了系统资源,适当使用可以提高系统性能
使用场景
需要频繁的创建和销毁对象、创建对象耗时过多但需要经常用到的、业务要求只能有一个实例的
三、方法
1.饿汉式
- 解析:所谓饿汉式即像恶狼一样,类一加载就迫不及待先创建出实例,私有化构造器使外部无法直接new对象
- 优点:写法简单,类一加载便实例化,线程安全
- 缺点:类一加载便实例化,没有懒加载的效果,如果类一直不需要实例,就会造成空间浪费
java
public class Pattern1 {
private Pattern1() {
}
// 饿汉式1
private static final Pattern1 instance = new Pattern1();
public static Pattern1 getInstance() {
return instance;
}
}
java
public class Pattern1 {
private Pattern1() {
}
// 饿汉式2
private static final Pattern1 instance;
static {
instance = new Pattern1();
}
public static Pattern1 getInstance() {
return instance;
}
}
java
public class Singleton {
public static void main(String[] args) {
Pattern1 instance1 = Pattern1.getInstance();
Pattern1 instance2 = Pattern1.getInstance();
System.out.println(instance1 == instance2); // true
System.out.println(instance1); // 非空
System.out.println(instance1.hashCode() == instance2.hashCode());// true
}
}
2、懒汉式
(1)懒汉式1(不可用)
- 解析:所谓懒汉式即像本身懒得创建单例,等到需要实例时再创建出实例
- 优点:实现懒加载
- 缺点:当多个线程同时进入instance == null地判断时可能会返回多个实例而不是单例,线程不安全
java
public class Pattern2 {
private static Pattern2 instance;
private Pattern2() {
}
public static Pattern2 getInstance() {
if (instance == null) {
instance = new Pattern2();
}
return instance;
}
}
(2)懒汉式2(不推荐)
- 目的:为了解决上述地线程安全问题
- 实现:实现线程安全很自然想到加同步锁,添加synchronized关键字
- 缺点:既然用到了锁,就容易出现效率问题;我们分析一下,其实只需要第一次获取实例地时候加锁就行,后续每次得到单例只需要返回单例没有线程安全问题,锁却还在,浪费时间
java
public class Pattern2 {
private static Pattern2 instance;
private Pattern2() {
}
public static synchronized Pattern2 getInstance() {
if (instance == null) {
instance = new Pattern2();
}
return instance;
}
}
(3)懒汉式3(失败)
- 目的:为了解决上述地线程安全效率低问题
- 实现:既然只有instance == null时才会出现线程安全,不如将锁加在判断语句中,当不为null自然不会进入锁
- 缺点:好像是解决了效率低下,然而杀敌一千自损一万,这种写法还是线程不安全的;当多个线程进入了判断语句内,instance = new Pattern2()这个语句就已经会执行多次,instance并不是单例
java
public class Pattern2 {
private static Pattern2 instance;
private Pattern2() {
}
public static Pattern2 getInstance() {
if (instance == null) {
synchronized (Pattern2.class) {
instance = new Pattern2();
}
}
return instance;
}
}
(4)懒汉式4(双重判断)
- 目的:为了解决上述效率高但是又导致线程安全问题
- 实现:既然将锁加在判断语句中,判断语句会有多个线程进来,不如在判断语句中的锁里面再进行一次判断,即进行双重判断
java
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;
}
}
此处instance必须加volatile注解,目的是保证变量的有序性 ,为什么要保证有序性,还得先讲讲new一个对象具体做的事
- 首先在常量池中去定位一个类的符号引用,并检查类是否被加载、连接、初始化过,没有就需要进行类加载
- 接下来为对象分配内存,有指针碰撞(连续内存使用)和空闲列表(碎片内存使用)两种分配方式
- 分配内存后为对象内存空间初始化为0(除对象头),那么对象不经过初始化便可使用
- 对对象头进行一些设置,如GC分代年龄,是否有偏向锁,hashcode(哈希码事实上在调用hashcode方法时才设置),线程ID,是否持有锁等设置
- 调用构造函数()方法给第三步初始化为0的字段按照用户要求设置
- 将引用指向新生成对象的地址
而上述这些步骤由于指令重排序的原因并不一定按顺序进行,在单线程这是没有问题的,因为单线程中执行new操作时这个线程不会同时又去使用它,一次只会执行一个操作,而多线程中如果线程A执行instance = new DoubleCheck()时先执行的new对象第6步再执行初始化对象,此时线程B判断instance == null为false(instance引用已经得到便不为null)则直接返回一个未初始化的对象并使用就出现问题了
这时给instance变量加上volatile保证new的过程不会进行重排序便不会出现问题
(5)懒汉式5(静态内部类)
- 优点:同样是既能实现懒加载,又是线程安全的
- 实现:利用类加载机制实现;静态内部类不会在Pattern3初始化时就加载而是会等到第一次调用getInstance方法时才会加载,这就实现了懒加载,而且JVM在加载静态内部类时是线程安全的
java
public class Pattern3 {
private Pattern3() {
}
private static class Provider {
private static Pattern3 instance = new Pattern3();
}
public static Pattern3 getInstance() {
return Provider.instance;
}
}
3、枚举类实现(官方推荐)
- 优点:线程安全的,而且不会被反射破坏单例(前面的例子事实上只要你想都能够手动使用反射破坏单例)
- 缺点:不是懒加载的
- 实现:使用枚举类实现单例,枚举类是从JVM层面保证单例的
java
public enum Pattern4 {
INSTANCE;
public static void test() {
System.out.println("Pattern4");
}
}
推荐写法
java
public class Pattern4 {
private Pattern4() {
}
public static Pattern4 getInstance() {
return Instance.INSTANCE.getInstance();
}
private enum Instance {
INSTANCE;
private Pattern4 pattern4;
// enum类保证了只会使用一次构造函数
Instance() {
this.pattern4 = new Pattern4();
}
public Pattern4 getInstance() {
return this.pattern4;
}
}
}
java
public class Singleton {
public static void main(String[] args) {
Pattern4 instance1 = Pattern4.INSTANCE;
Pattern4 instance2 = Pattern4.INSTANCE;
instance1.test();// 打印出Pattern4
System.out.println(instance1.hashCode() == instance2.hashCode());// true
}
}
四、总结
- 单例模式保证系统中这个类只有一个对象,节省了系统资源,适当使用可以提高系统性能
- 使用场景:需要频繁的创建和销毁对象、创建对象耗时过多但需要经常用到的、业务要求只能有一个实例的
- 对于饿汉式,实现比较简单易懂也线程安全,如果能保证类会至少使用一次完全是可以使用的
- 对于懒汉式,主要的就是注意线程安全问题
如果您觉得该文章有用,欢迎点赞、留言并分享给更多人。感谢您的支持!