深入理解单例模式

new Thread(() -> {

LazyMan.getInstance();

}).start();

}

}

image-20210205165553057

【3】方法三:懒汉单例加强(同步方法)

方法二存在线程不安全的隐患,我们可以通过加锁来进行加强,方法声明上加上 synchronized,同步方法

  • 和方法二一样,在调用实例方法的时候才去实例化对象
  • 在方法声明上加锁,保证线程安全
  • 优点:一直没人用的话,就不会创建实例,节约内存空间,且线程安全
  • 缺点:每次创建实例都要判断锁,浪费判断时间,降低效率(时间换空间)

public class LazyManSyn {

// 私有构造器

private LazyManSyn(){}

// 私有静态引用创建对象

private static LazyManSyn lazyManSyn = null;

// 同步方法创建实例对象

public static synchronized LazyManSyn getInstance(){

if (lazyManSyn == null){

lazyManSyn = new LazyManSyn();

}

return lazyManSyn;

}

}

// 多线程并发测试

@Test

public void TestMain3(){

for (int i = 0; i < 10; i++) {

new Thread(()->{

System.out.println(LazyManSyn.getInstance().hashCode());

}).start();

}

}

image-20210206103944913

【4】方法四:懒汉单例加强(DCL)

方法三虽然线程安全,但每次创建实例都需要判断锁,效率低,我们可以通过双重锁来进行加强,让已经有实例对象的时候不进行锁判断,这种方式称之为DCL懒汉式

  • 和方法二一样,在调用实例方法的时候才去实例化对象
  • 通过双重锁来防止多实例(双重判断和锁)
  • 第一个 if 判断:提高执行效率,如果对象不为空,就直接不执行下面的程序
  • 第二个 if 判断:在第一个 if 判断和 synchronized 锁之间,可以进来多个线程,如果没有第二个 if 判断,一个线程拿到锁 new 对象后释放锁,第二个线程又能够拿到锁 new 对象,通过第二个 if 则可以避免创建多个对象。
  • synchronized 锁:通过同步代码块
  • 优点:减少了锁的判断,相对于方法三提升了效率,使用双重锁,暂且认为线程安全(后面再说)
  • 缺点:其实这个方法还是存在线程不安全的隐患,在lazyManDCL = new LazyManDCL();创建对象的时候,不是一个原子性操作,会出现指令重排的可能,因此也可能创建多个实例

public class LazyManDCL {

// 私有构造器

private LazyManDCL(){}

// 指向自己实例的私有静态引用

private static LazyManDCL lazyManDCL = null;

// 双重检查锁模式

public static LazyManDCL getInstance(){

if(lazyManDCL == null){

synchronized (LazyManDCL.class){

if(lazyManDCL == null){

lazyManDCL = new LazyManDCL();

}

}

}

return lazyManDCL;

}

}

// 多线程并发测试

@Test

public void TestMain3(){

for (int i = 0;i < 10;i++){

new Thread(() -> {

System.out.println(LazyManDCL.getInstance().hashCode());

}).start();

}

}

image-20210206103944913

【5】方法五:DCL加强

在DCL单例模式中,虽然能够通过双重锁保证一定的线程安全性,但是在 new 对象的时候,非原子性操作造成指令重排,执行 new LazyManDCL(); 的过程如下:

  1. 分配内存空间
  1. 执行构造方法
  1. 把这个对象指向这个空间

执行代码时,为了提高性能,编译器和处理器往往会对指令进行重排序,上面的执行顺序可能是 1—>2—>3,也可能是 1—>3—>2,当执行顺序为 1—>3—>2 时,就会出现如下问题:

  • A线程执行 1—>3 ,分配了内存空间,把这个对象指向这个空间
  • 此时B线程进来,由于A线程指向了这个空间,造成第一个 if 判断为 false,从而直接 return 对象,由于没有初始化对象,就会报错

因此我们要防止指令重排的现象发生,即:使用 volatile 关键字,代码如下,就是在方法四的基础上加了一个 volatile 关键字

  • 优点:线程安全
  • 缺点:多重判断,浪费时间,降低效率

public class LazyManDCL {

// 私有构造器

private LazyManDCL(){}

// 指向自己实例的私有静态引用,使用volatile防止指令重排

private static volatile LazyManDCL lazyManDCL = null;

// 双重检查锁模式

public static LazyManDCL getInstance(){

if(lazyManDCL == null){

synchronized (LazyManDCL.class){

if(lazyManDCL == null){

lazyManDCL = new LazyManDCL();

}

}

}

return lazyManDCL;

}

}

// 多线程并发测试

@Test

public void TestMain4(){

for (int i = 0;i < 10;i++){

new Thread(() -> {

System.out.println(LazyManDCL.getInstance().hashCode());

}).start();

}

}

image-20210206115800861

【6】方法六:懒汉单例加强(静态内部类)

通过静态内部类来创建实例,静态内部类单例模式的核心原理为对于一个类,JVM在仅用一个类加载器加载它时,静态变量的赋值在全局只会执行一次!,

  • 优点1:外部类加载时,不会立即加载内部类,因此不占内存
  • 优点2:不像DCL那样需要进行多重判断,提升了效率
  • 优点3:第一次调用getInstance()方法时,虚拟机才加载SingleOne类,既能保证线程安全,又能保证实例的唯一性,同时也延迟了单例的实例化

public class SingleOne {

// 私有构造方法

private SingleOne(){}

// 通过静态内部类创建实例

private static class InnerClass{

private static final SingleOne SINGLE_ONE = new SingleOne();

}

// 通过内部类返回对象实例

public static SingleOne getInstance(){

return InnerClass.SINGLE_ONE;

}

}

// 多线程并发测试

@Test

public void TestMain5(){

for (int i = 0; i < 10; i++) {

new Thread(()->{

System.out.println(SingleOne.getInstance().hashCode());

}).start();

}

}

image-20210206185449387

【6】方法七:枚举单例(最完美写法)

上面的方法都是在忽略反射的情况下,我们都知道,反射可以破坏类,能够无视私有构造器,因此,上面的单例都可以使用反射进行破坏,为了解决反射破坏,我们可以使用枚举单例。

  • 枚举的特性本身就是单例,在任何情况下都是一个单例
  • 直接通过EnumSingle.INVALID进行调用
  • 让JVM来帮我们保证线程安全和单一实例问题

public enum EnumSingle {

INVALID;

public void doSomething(){}

}

// 多线程并发测试

@Test

public void TestMain6(){

for (int i = 0; i < 10; i++) {

new Thread(()->{

System.out.println(EnumSingle.INVALID.hashCode());

}).start();

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

面试是跳槽涨薪最直接有效的方式,马上金九银十来了,各位做好面试造飞机,工作拧螺丝的准备了吗?

掌握了这些知识点,面试时在候选人中又可以夺目不少,暴击9999点。机会都是留给有准备的人,只有充足的准备,才可能让自己可以在候选人中脱颖而出。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
unity.csdnimg.cn/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />

最后

面试是跳槽涨薪最直接有效的方式,马上金九银十来了,各位做好面试造飞机,工作拧螺丝的准备了吗?

掌握了这些知识点,面试时在候选人中又可以夺目不少,暴击9999点。机会都是留给有准备的人,只有充足的准备,才可能让自己可以在候选人中脱颖而出。

[外链图片转存中…(img-4uiOVshg-1713749245750)]

[外链图片转存中…(img-SIjQwlQt-1713749245750)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值