设计模式(一) 单例

饿汉式

  • 优点: 不用考虑多线程问题,利用clinit机制实现单例对象
  • 缺点:类加载时,即使用不到当前对象也要创建对象,占用内存
/**
 * 饿汉式
 * 私有化静态空参构造器
 * 初始化静态变量
 * 添加getInstance方法
 */
public class SingleTon {
    
    
    private static SingleTon instance = new SingleTon();
    private SingleTon() {
    
    }
    public static SingleTon getInstance() {
    
    
        return instance;
    }
}

懒汉式

加锁双重校验

  • 优点: 当对象第一次使用时才会被创建
  • 缺点: 相对于后两种代码相对复杂, synchronized会成为系统瓶颈
  • 加锁所双重校验的方式不仅可以用在单例模式中,在多线程环境下控制方法执行一次过程中,如: 实现读取配置文件,根据一个id生成一个对应文件等场景都可以使用,而且很灵活.

为什么要判断两次instance是否为null

  • 如果去掉外层的判null,则进入getInstance方法时,就会锁定当前类class,此时如果instance已经初始化过每次获取实例对象都会进行一次加锁操作,非常消耗性能.
  • 如果去掉内层循环,instance如果没有初始化,经过外层判断后,大量线程进入等待锁状态,得到锁的线程初始化instance成功后之后的每一个线程都会重新初始化一次instance, 这就失去了加锁的意义

为什么instance要添加volatile关键字

  • volatile 的作用有两个
    1. 变量修改时会通知其他线程修改
    2. 防止指令重排序
  • 查看一个Object o = new Object();的过程.
// 在堆中以Object对象申请一块内存,并将地址压入操作数栈
1. new #5 <java/lang/Object>
// 复制操作数栈顶的值
2. dup
// 调用构造方法
3. invokespecial #1 <java/lang/Object.<init>>
// 将栈顶的地址值复制给局部变量表中1号位(即变量o)
4. astore_1
// 结束
5. return
  • 因为步骤3执行前,对象的地址已经出现在了操作数栈顶的位置,所以单线程中,构造方法的执行与将地址赋值给变量不存在因果关系,所以会存在步骤4 要早于步骤3执行情况, 即o已经不是null了,但是o指向的是还没有执行构造方法的Object对象.
  • 回到getInstance方法, 同样,当下一个线程进来的时候,发现instance已经不是null,但是此时的instance还没有执行过构造方法,调用者得到的则是一个实例的"半成品"
  • 使用volatile关键字,就是防止以上指令被重排序的情况.
/**
 * 懒汉式
 * 私有化静态空参构造器
 * 添加getInstance方法
 *  判断instance是否为null
 *  对当前类class对象加锁
 *  如果进入方法后实例对象仍旧为null则初始化对象
 */
public class SingleTon {
    
    
    private static volatile SingleTon instance;
    private SingleTon() {
    
    }
    public static SingleTon getInstance() {
    
    
        if (null == instance) {
    
    
            synchronized (SingleTon.class) {
    
    
                if (null == instance) {
    
    
                    instance = new SingleTon();
                }
            }
        }
        return instance;
    }
}

静态内部类的方式

  • 优点: 使用内部类的加载机制实现懒加载, 官方推荐
  • 缺点: 创建对象流程复杂时可能不太灵活
/**
 * 懒汉式
 * 私有化静态空参构造器
 * 私有化静态内部类
 * 内部类中创建对象
 * getInstance方法直接返回内部类中的instance
 */
public class SingleTon {
    
    
    private static class SingletonHolder {
    
    
        private static final SingleTon INSTANCE = new SingleTon();
    }
    private SingleTon (){
    
    }
    public static final SingleTon getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}

枚举

  • 枚举值就是单一实例,而且线程安全,懒加载,代码简单
  • 缺点: 创建对象流程复杂时可能不太灵活
/**
 * 懒汉式
 * 使用枚举实例化
 */
public enum SingleTon {
    
    
   INSTANCE;
}

猜你喜欢

转载自blog.csdn.net/liha12138/article/details/106863946