java 单例的意思_聊聊Java中的单例模式

本文详细介绍了Java中的四种单例模式实现:饿汉模式、饱汉模式(双重检查锁定)、静态内部类和枚举单例。通过实例解析了每种模式的原理、优缺点,并强调了volatile关键字在并发编程中的重要性。对于Java开发者来说,理解并掌握这些单例模式有助于提高代码质量。
摘要由CSDN通过智能技术生成

本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: Nicksxs

创建时间: 2019年12月21日

本文链接:聊聊Java中的单例模式​nicksxs.me3b68d4cdc22478fb3cb86e99112eaac9.png

这是个 Java 面试的高频问题,我也遇到过,以往都是觉得这类题没意思,网上一搜一大堆,也不愿意记,其实说回来,主要还是没静下心来好好去理解,今天无意中看到一个课程,基本帮我把一些疑惑的点讲清楚了,首先单例是啥意思,这个其实是有范围一说,比如我起了个Spring Boot应用,在这个应用范围内,我的常规 bean 是单例的,意味着 getBean 的时候其实永远只会拿到那一个对象,那要怎么来写一个单例呢,首先就是传说中的饿汉模式,也是最简单的

饿汉模式

public class Singleton1 {

// 首先,将构造方法变成私有的 private Singleton1() {};

// 创建私有静态实例,这样第一次使用的时候就会进行创建 private static Singleton instance = new Singleton1();

// 使用这个对象都是通过这个 getInstance 来获取 public static Singleton1 getInstance() {

return instance;

}

// 瞎写一个静态方法。这里想说的是,如果我们只是要调用 Singleton.getDate(...), // 本来是不想要生成 Singleton 实例的,不过没办法,已经生成了 public static Date getDate(String mode) {return new Date();}

}

上面借鉴了一些代码,其实这是最基本,也不会错的方法,但是正如其中getDate方法里说的问题,有时候并没有想那这个对象,但是因为我调用了这个类的静态方法,导致对象已经生成了,可能这也是饿汉模式名字的来由,不管三七二十一给你生成个单例就完事了,不管有没有用,但是这种个人觉得也没啥大问题,如果是面试的话最好说出来它的缺点

饱汉模式

public class Singleton2 {

// 首先,也是先堵死 new Singleton() 这条路,将构造方法变成私有 private Singleton2() {}

// 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的 private static volatile Singleton2 instance = null;

private int m = 9;

public static Singleton getInstance() {

if (instance == null) {

// 加锁 synchronized (Singleton2.class) {

// 这一次判断也是必须的,不然会有并发问题 if (instance == null) {

instance = new Singleton2();

}

}

}

return instance;

}

}

这里容易错的有三点,理解了其实就比较好记了

第一点,为啥不在 getInstance 上整个代码块加 synchronized,这个其实比较容易理解,就是锁的力度太大,性能太差了,这点其实也要去理解,可以举个夸张的例子,比如我一个电商的服务,如果为了避免一个人的订单出现问题,是不是可以从请求入口就把他锁住,到请求结束释放,那么里面做的事情都有保障,然而这显然不可能,因为我们想要这种竞态条件抢占资源的时间尽量减少,防止其他线程等待。 第二点,为啥synchronized之已经检查了 instance == null,还要在里面再检查一次,这个有个术语,叫 double check lock,但是为啥要这么做呢,其实很简单,想象当有两个线程,都过了第一步为空判断,这个时候只有一个线程能拿到这个锁,另一个线程就等待了,如果不再判断一次,那么第一个线程新建完对象释放锁之后,第二个线程又能拿到锁,再去创建一个对象。 第三点,为啥要volatile关键字,原先对它的理解是它修饰的变量在 JMM 中能及时将变量值写到主存中,但是它还有个很重要的作用,就是防止指令重排序,instance = new Singleton();这行代码其实在底层是分成三条指令执行的,第一条是在堆上申请了一块内存放这个对象,但是对象的字段啥的都还是默认值,第二条是设置对象的值,比如上面的 m 是 9,然后第三条是将这个对象和虚拟机栈上的指针建立引用关联,那么如果我不用volatile关键字,这三条指令就有可能出现重排,比如变成了 1-3-2 这种顺序,当执行完第二步时,有个线程来访问这个对象了,先判断是不是空,发现不是空的,就拿去直接用了,是不是就出现问题了,所以这个volatile也是不可缺少的

嵌套类

public class Singleton3 {

private Singleton3() {}

// 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性 private static class Holder {

private static Singleton3 instance = new Singleton3();

}

public static Singleton3 getInstance() {

return Holder.instance;

}

}

这个我个人感觉是饿汉模式的升级版,可以在调用getInstance的时候去实例化对象,也是比较推荐的

枚举单例

public enum Singleton {

INSTANCE;

public void doSomething(){

//todo doSomething }

}

枚举很特殊,它在类加载的时候会初始化里面的所有的实例,而且 JVM 保证了它们不会再被实例化,所以它天生就是单例的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值