java手写单例模式_java架构之路(多线程)大厂方式手写单例模式

本文介绍了Java中单例模式的实现,特别是针对多线程场景下的懒汉式和饿汉式单例模式。通过分析代码和讨论并发问题,解释了如何使用volatile关键字确保线程安全,以及并发环境下单例模式的正确实现。
摘要由CSDN通过智能技术生成

上期回顾:

上次博客我们说了我们的volatile关键字,我们知道volatile可以保证我们变量被修改马上刷回主存,并且可以有效的防止指令重排序,思想就是加了我们的内存屏障,再后面的多线程博客里还有说到很多的屏障问题。

10182c3a11ac119e6e992352dff0b512.png

volatile虽然好用,但是别用的太多,咱们就这样想啊,一个被volatile修饰的变量持续性的在修改,每次修改都要及时的刷回主内存,我们讲JMM时,我们的CPU和主内存之间是通过总线来连接的,也就是说,每次我们的volatile变量改变了以后都需要经过总线,“道路就那么宽,持续性的通车”,一定会造成堵车的,也就是我们的说的总线风暴。所以使用volatile还是需要注意的。

单例模式:

属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例),就是说每次我们创建的对象成功以后,在一个线程中有且仅有一个对象在正常使用。可以分为懒汉式和饿汉式。

懒汉式就是什么意思呢,创建时并没有实例化对象,而是调用时才会被实例化。我们来看一下简单的代码。

public classLasySingletonMode {public static voidmain(String[] args) {

LasySingleton instnace=LasySingleton.getInstnace();

}

}classLasySingleton {/*** 私有化构造方法,禁止外部直接new对象*/

privateLasySingleton() {

}/*** 给予一个对象作为返回值使用*/

private staticLasySingleton instnace;/*** 给予一个获取对象的入口

*

*@returnLasySingleton对象*/

public staticLasySingleton getInstnace() {if (null ==instnace) {

instnace= newLasySingleton();

}returninstnace;

}

}

看起来很简单的样子,私有化构造方法,给予入口,返回对象,差不多就这样就可以了,但是有一个问题,如果是多线程呢?

public staticLasySingleton getInstnace() {if (null ==instnace) {

instnace= newLasySingleton();

}returninstnace;

}

我们假想两个线程,要一起运行这段代码,线程A进来了,看到instnace是null的,ε=(´ο`*)))唉,线程B进来看见instnace也是null的(因为线程A还没有运行到instnace = new LasySingleton()这个代码),这时就会造成线程A,B创建了两个对象出来,也就不符合我们的单例模式了,我们来改一下代码。

public staticLasySingleton getInstnace() {if (null ==instnace) {synchronized (LasySingleton.class){

instnace= newLasySingleton();

}

}returninstnace;

}

这样貌似就可以了,就算是两个线程进来,也只有一个对象可以拿到synchronized锁,就不会产生new 两个对象的行为了,其实不然啊,我们还是两个线程来访问我们的这段代码,线程A和线程B,两个线程来了一看,对象是null的,需要创建啊,于是线程A拿到锁,开始创建,线程B继续等待,线程A创建完成,返回对象,将锁释放,这时线程B可以获取到锁(因为null == instnace判断已经通过了,在if里面进行的线程等待),这时线程B还是会创建一个对象的,这显然还是不符合我们的单例模式啊,我们来继续改造。

public staticLasySingleton getInstnace() {if (null ==instnace) {synchronized (LasySingleton.class){if (null ==instnace) {

instnace= newLasySingleton();

}

}

}returninstnace;

}

这次基本就可以了吧,回想一下我们上次的volatile有序性,难道真的这样就可以了吗?instnace = new LasySingleton()是一个原子操作吗?有时候你面试小厂,这样真的就可以了,我们来继续深挖一下代码。看一下程序的汇编指令码,首先找我们的class文件。运行javap -c ****.class。

E:\IdeaProjects\tuling-mvc-3\target\classes\com\tuling\control>javap -c LasySingleton.class

Compiled from"LasySingletonMode.java"class com.tuling.control.LasySingleton {

public static com.tuling.control.LasySingleton getInstnace();

Code:0: aconst_null1: getstatic #2 // Field instnace:Lcom/tuling/control/LasySingleton;4: if_acmpne 17

7: new #3 // class com/tuling/control/LasySingleton10: dup11: invokespecial #4 // Method "":()V14: putstatic #2 // Field instnace:Lcom/tuling/control/LasySingleton;17: getstatic #2 // Field instnace:Lcom/tuling/control/LasySingleton;20: areturn

}

不是很好理解啊,我们只想看instnace = new LasySingleton()是不是一个原子操作,我们可以这样来做,创建一个最简单的类。

public classDemo {public static voidmain(String[] args) {

Demo demo= newDemo();

}

}

然后我们运行javap -c -v ***.class

E:\IdeaProjects\tuling-mvc-3\target\classes>javap -c -v Demo.class

Classfile/E:/IdeaProjects/tuling-mvc-3/target/classes/Demo.class

Last modified2020-1-13; size 389bytes

MD5 checksum f8b222a4559c4bf7ea05ef086bd3198c

Compiled from"Demo.java"public class Demo

minor version:0major version:49flags: ACC_PUBLIC, ACC_SUPER

Constant pool:#1 = Methodref #4.#19 // java/lang/Object."":()V#2 = Class #20 //Demo#3 = Methodref #2.#19 // Demo."":()V#4 = Class #21 // java/lang/Object#5 = Utf8

#6 =Utf8 ()V#7 =Utf8 Code#8 =Utf8 LineNumberTable#9 =Utf8 LocalVariableTable#10 =Utf8 this#11 =Utf8 LDemo;#12 =Utf8 main#13 = Utf8 ([Ljava/lang/String;)V#14 =Utf8 args#15 = Utf8 [Ljava/lang/String;#16 =Utf8 demo#17 =Utf8 SourceFile#18 =Utf8 Demo.java#19 = NameAndType #5:#6 // "":()V#20 =Utf8 Demo#21 = Utf8 java/lang/Object

{

public Demo();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_01: invokespecial #1 // Method java/lang/Object."":()V4: returnLineNumberTable:

line1: 0LocalVariableTable:

Start Length Slot Name Signature0 5 0this LDemo;

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=2, args_size=1

0: new #2 //class Demo3: dup4: invokespecial #3 // Method "":()V7: astore_18: returnLineNumberTable:

line3: 0line4: 8LocalVariableTable:

Start Length Slot Name Signature0 9 0 args [Ljava/lang/String;8 1 1demo LDemo;

}

SourceFile:"Demo.java"E:\IdeaProjects\tuling-mvc-3\target\classes>

结果是这样的,我们来分析一下代码,先看这个

0: new #2 // class Demo

就是什么意思呢?我们要给予Demo对象在对空间上开辟一个空间,并且返回内存地址,指向我们的操作数栈的Demo对象

3: dup

是一个对象复制的过程。

4: invokespecial #3 // Method "":()V

见名知意,init是一个初始化过程,我们会把我们的刚才开辟的栈空间进行一个初始化,

7: astore_1

这个就是一个赋值的过程,刚才我们有个复制的操作对吧,这时会把我们复制的一个对象赋值给我们的栈空间上的Demo,是不是有点蒙圈了,别急,后面的简单。

这是一个对象的初始化过程,在我的JVM系列博客简单的说过一点,后面我会详细的去说这个,总结起来就是三个过程。

1.开辟空间2.初始化空间3.给引用赋值

这个代码一般情况下,会按照123的顺序去执行的,但是超高并发的场景下,可能会变为132,考虑一下是不是,我们的as-if-serial,132的执行顺序在单线程的场景下也是合理的,如果真的出现了132的情况,会造成什么后果呢?回到我们的单例模式,所以说我们上面单例模式代码还需要改。

public classLasySingletonMode {public static voidmain(String[] args) {

LasySingleton instnace=LasySingleton.getInstnace();

}

}classLasySingleton {/*** 私有化构造方法,禁止外部直接new对象*/

privateLasySingleton() {

}/*** 给予一个对象作为返回值使用*/

private static volatileLasySingleton instnace;/*** 给予一个获取对象的入口

*

*@returnLasySingleton对象*/

public staticLasySingleton getInstnace() {if (null ==instnace) {synchronized (LasySingleton.class) {if (null ==instnace) {

instnace= newLasySingleton();

}

}

}returninstnace;

}

}

这样来写,就是一个满分的单例模式了,无论出于什么样的考虑,都是满足条件的。也说明你真的理解了我们的volatile关键字。

饿汉式相当于懒汉式就简单很多了,不需要考虑那么多了。

packagecom.tuling.control;public classHungrySingletonMode {public static voidmain(String[] args) {

String name=HungrySingleton.name;

System.out.println(name);

}

}classHungrySingleton {/*** 私有化构造方法,禁止外部直接new对象*/

privateHungrySingleton() {

}private static HungrySingleton instnace = newHungrySingleton();public static String name = "XXX";static{

System.out.println("我被创建了");

}public staticHungrySingleton getInstance(){returninstnace;

}

}

很简单,也不是属于我们多线程范畴该说的,这里就是带着说了一下,就是当我们调用内部方法时,会主动触发对象的创建,这样就是饿汉模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值