手撕单例模型

面试的时候经常会碰到的一些手撕题目,平时不写面试面试根本没思路。常见的主要有手撕单利、手撕生产者消费者、多个线程轮流打印等等。

单例模型

单例模型涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模型特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例

单例模式的关键点:

构造方法不对外开放,为private
确保单例类只有一个对象,尤其是多线程模式下
通过静态方法或枚举返回单例对象
确保单例类在反序列化是不会重新创建新的对象

实现方式

饿汉式

饿汉式会提前把需要的单例对象创建好,需要用的时候直接去获取即可,不需要创建。好处是方便、安全,省去了创建的时间,而提前创建时不会出现线程安全等问题。坏处是可能会造成资源的浪费,不能延迟加载,因为创建单例时无法判断此单例是否会使用,可能永远不会使用,或者再很久之后才会使用,提前创建浪费了资源。

public class Singleton1 {

    private static Singleton1 singleton1 = new Singleton1();

    private Singleton1(){ }

    public static Singleton1 getInstance(){
        return  singleton1;
    }
}

懒汉式

懒汉式会等到需要的时候才去创建,不会提前创建。好处是资源的高效利用,但是可能存在线程安全问题,因此必须进行线程同步。

	private static Singleton2 instance;

    private Singleton2(){}

    public static synchronized  Singleton2 getInstance(){
        if(instance == null){instance = new Singleton2();}
        return  instance;
  	}
  }

上面是在getInstance添加synchronized,进行线程同步,效率并不高,因为不管instance是否被创建,都会进行线程同步,效率比较低。更好的应该是再没创建的时候进行同步,创建之后不需要同步,直接返回即可。如下第一次检查instance不为null,就不需要执行下面的加锁的初始化操作,因此可以降低synchronized带来的性能开销。第二次检查instance避免了同时有多个线程通过了第一个instance的检查。

public class Singleton2 {
    private volatile static Singleton2 instance;

    private Singleton2(){}

    public Singleton2 getInstance(){
        if(instance == null){
            synchronized (Singleton2.class){
                if(instance == null){
                    instance = new Singleton2();
                }
            }
        }
        return  instance;
    }
}

instance被volatile关键词修饰,可以避免指令重排。new关键字创建一个对象分为3步:
1. 为instance分配内存空间
2. 初始化instance
3. 为instance指向分配内存的内存地址
JVM可能进行指令重排,如果是按照正常的1->2->3顺序来执行,那么肯定不会有问题。但是如果指令重排,变为1->3->2,instance指向分配的内存地址时,instance并没有初始化完成。如果其他线程此时进入获取instance,会判断instance非null,判断已经被创建,但是instance并没有实例化完成,如果此时使用instance就会造成各种各样的问题。使用volatile通过插入内存屏障可以禁止JVM指令重排序,保证在多线程环境下也能正常的运行。

静态内部类方式实现单例

在类的加载时,会初始化静态变量、静态代码块、静态方法,但是不会加载内部类和静态内部类。所以静态内部类并不会先行加载,只有在使用到静态内部类的时候才会加载。而类的加载(不管是静态内部类还是普通类),JVM都会进行线程同步。所以此种方法可以在需要的时候再加载instance,并且保证线程安全。

此处单例模式是在于用的final关键字,当final修饰引用类型的变量的话,则在其初始化后就不能然后其指向另一个对象,保证只初始化一次。同时final内存语义也防止指令重排,即对这个对象赋值要在初始化完成之后。

public class Singleton3 {
    public static class SingletonHolder{
        private static  final Singleton3 instance = new Singleton3();
    }

    private Singleton3(){}

    public static final Singleton3 getInstance(){
        return SingletonHolder.instance;
    }
}

枚举实现

public enum Singleton4 {
    INSTANCE;
    private String name;
    public String getName(){
        return  name;
    }

    public void setName(String name){
        this.name = name;
    }	
}

使用SingletonEnum.INSTANCE进行访问,无需再定义getInstance方法和调用该方法。
JVM对枚举实例的唯一性,避免了上面提到的反序列化和反射机制破环单例的情况出现:每一个枚举类型和定义的枚举变量在JVM中都是唯一的。 原因:枚举类型在序列化时仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值