聊聊常见设计模式

单例模式

单一实例,某个类在内存中只能存在单一实例对象,或者说一个VM实例中只有一个某个类的实例。

单例模式最大的特点就是节省系统开销,例如配置类、工具类这种“无状态”的类,可以设计为单例类。但是如果“有状态”如用户,则单例模式可能存在线程安全问题。

如果将一个有状态类设计为单例类,如用户,那么多个线程持有的引用将会指向单例对象,如果访问共享资源的方法不是“原子的”,那么就存在线程安全问题。

单例模式的基本实现:
【1】构造器是私有的,外界不能主动创建对象
【2】通过公有静态方法向外提供获取对象的方法
【3】单例类仅能创建一次单例对象,一旦创建便使用一个静态成员指针指向。

饿汉式

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

饿汉式,单例的创建是在类初始化阶段进行的,也就是在<clinit>()方法中进行的,该方法的线程安全由JVM保障(类初始化锁)。也可以将创建动作写在静态块中,本质上都是在<clinit>()方法进行初始化。

java规定,每一个类或者接口,都有一个唯一的初始化锁与之对应。JVM在类初始化过程中会获取这个初始化锁,并且每个线程至少获取一次锁来确保这个类已经被初始化过了,如果存在竞争那么未抢到类初始化锁的线程就会等待。

饿汉式的特点就是线程安全,这个线程安全是由JVM隐式实现的。

懒汉式

懒汉式的特点就是:延迟实例化,降低了初始化类或者创建实例的开销。

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

以上的写法在单线程环境下没有错误,但是多线程环境下会出现线程不安全问题。
【1】getInstance()方法不是同步方法,使得创建对象、返回引用这个动作不是原子的。因此可以创建大量对象
【2】多线程环境下,线程彼此之间存在可见性问题,因此无法及时感知到instance的变化

线程安全懒汉式

注意:这里的“线程安全”,指的是将“获得实例”这个动作封装为原子的动作,不存在“创建对象的时候,被其他线程干扰的情况”

如果单从java线程的角度思考,可以理解为“同步块内不进行切换”,但是java线程是对操作系统线程的进一步封装和抽象,因此即使线程进入同步块,操作系统层面仍然会发生切换,只不过java有意的为用户屏蔽这些细节——将注意力放在java线程而不是操作系统线程。

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

volatile修饰instance,保证了instance对线程的看见性。当某一线程成功执行了instance赋值操作,那么其他所有线程都可以立即看到instance的最新值。
使用同步块包裹了操作共享变量instance的代码,同一时刻仅有一个线程会执行instance赋值操作。剩余一部分阻塞在synchronized对应的同步队列,一旦获得锁后,判断instance状态后得知已经指向对象,则退出。另一部分线程在外部发现instance已经指向对象,也将直接返回。

此外,此处的instance使用volatile修饰还有另外一个目的:禁止重排序

new 指令 可以 分解为【1】new【2】dup【3】invokeSpecial【4】a_store
大致对应:分配内存、初始化、返回引用
为了提升系统性能,处理器和编译器会在保证单线程运行正确的前提下(as-if-serial)对指令进行重排序,但是多线程环境下重排序可能导致出错。

例如,将new中的指令重排序后变成:分配内存、返回引用、初始化。
如果一个线程A在同步块中创建对象后,这时另外一个线程B便从外部拿到了这个未初始化对象的引用。

重排序在单线程环境下没问题,因为保证线程A正式访问这个instance之前,初始化操作完成即可。但是A访问instance之前,B先访问了instance,这时就出现问题了,B拿到了instance对象但是成员全部都是零值。

使用volatile可以返回引用与成员初始化之间重排序,保证线程拿到引用后,引用指向的对象已经完成初始化了。

基于静态内部类

class Singleton{
   
    private Singleton(){
   }
    private static 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值