单例模式学习笔记(详细)

单例模式

一、什么是单例模式

单例模式是一种常见的设计模式,定义是这个类只允许有一个实例对象存在

二、使用场景

购买东西时的购物车,window系统的回收站等等

三、实现方式

1. 懒汉式

public class Lazy {
    private static Lazy instance;
    private Lazy(){}
    public static Lazy getInstance(){
        if(instance == null){
            instance = new Lazy();
        }
        return instance;
    }
}

懒汉式,实行延迟加载,不会初始化,在需要用到实例的时候才去创建,用到的时候先检查实例存不存在,存在就返回,不存在创建一个返回,可以再单线程下使用,多线程下是不安全的,一个线程通过了判空判断的同时,另一个线程也通过了判空判断,就会同时创建多个实例,想要线程安全可以加synchronized关键字,但是会影响效率,不推荐。

  • 优点:延迟加载,不会浪费内存
  • 缺点:线程不安全,加synchronized会影响效率

2. 饿汉式

public class Hungry {
    private static Hungry instance = new Hungry();
    private Hungry(){}
    public static Hungry getInstance(){
        return instance;
    }
}

饿汉式,在初始化的时候创建实例,不会存在线程安全问题

  • 优点:线程安全
  • 缺点:没有延迟加载效果,如果没有用到这个实例就会浪费内存

3. 双检锁

public class DoubleCheck{
    private volatile static DoubleCheck instance;
    private DoubleCheck(){}
    public static DoubleCheck getInstance(){
        if(instance == null){
            synchronized(DoubleCheck.class){
                if(instance == null){
                    instance = new DoubleCheck();
                }
            }
        }
        return instance;
    }       
}

双检锁,外面一层if判断对象存在就不会执行加锁代码,提高了效率,synchronized加上里面一层 if 保证了线程安全

  • 优点: 线程安全,延迟加载,效率高,推荐

为什么加volatile关键字

原因是 instance = new DoubleCheck(); 这句代码不是原子性的。

创建一个对象分为三步:

  1. 分配 instance 对象内存空间 N
  2. 在内存 N 初始化对象
  3. 把 N 的地址赋值给对象 instance

这时在实例instance指向N的时候,instance是不为空的

但是,编译时编译器可能会将2,3顺序重新排序,造成顺序为1-3-2

  1. 分配 instance 对象内存空间 N
  2. 把 N 的地址赋值给对象 instance
  3. 在内存 N 初始化对象

线程A,在内存 N 初始化对象之前就将 N 的地址赋值给了instance
这时线程B调用了getInstance方法,发现instance不为null
这个时候instance没有初始化对象,线程B会将这个未初始化的对象返回,线程B在使用instance对象时就会出现问题

使用volatile修饰instance可以防止指令重排序,就可以避免这种现象发生。

4. 静态内部类

public static class Single{
    private static class Singleton{
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton(){}
    public static final Singleton getInstance(){
        return Singleton.INSTANCE;
    }
}

静态内部类,在第一次使用时才会初始化内部类Singleton,创建实例,保证了只有一个实例,并实现了延迟加载,加上静态域是线程安全的,减少了synchronized的开销。

  • 优点:线程安全,延迟加载,没有锁开销,效率高,推荐。

四、总结

这四种是比较常见的单例模式实现方式

  • 懒汉式实现了延迟加载,效率较高,但是线程不安全,适合单线程的时候使用
  • 饿汉式在初始化的时候创建对象,保证了线程安全,但是在没有使用到这个对象的时候就浪费了内存空间,可以在多线程使用
  • 双检索整合了懒汉式和饿汉式的优点,是线程安全,又实现了延迟加载,可以在多线程使用
  • 静态内部类方式也是拥有懒汉式和饿汉式的优点,线程安全,延迟加载,还减少了锁的开销,提高了效率,推荐使用这种方式
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。