java架构之路(多线程)大厂方式手写单例模式
上期回顾:
上次博客我们说了我们的volatile关键字,我们知道volatile可以保证我们变量被修改马上刷回主存,并且可以有效的防止指令重排序,思想就是加了我们的内存屏障,再后面的多线程博客里还有说到很多的屏障问题。
volatile虽然好用,但是别用的太多,咱们就这样想啊,一个被volatile修饰的变量持续性的在修改,每次修改都要及时的刷回主内存,我们讲JMM时,我们的CPU和主内存之间是通过总线来连接的,也就是说,每次我们的volatile变量改变了以后都需要经过总线,“道路就那么宽,持续性的通车”,一定会造成堵车的,也就是我们的说的总线风暴。所以使用volatile还是需要注意的。
单例模式:
属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例),就是说每次我们创建的对象成功以后,在一个线程中有且仅有一个对象在正常使用。可以分为懒汉式和饿汉式。
懒汉式就是什么意思呢,创建时并没有实例化对象,而是调用时才会被实例化。我们来看一下简单的代码。
public class LasySingletonMode {
public static void main(String[] args) {
LasySingleton instnace = LasySingleton.getInstnace();
}
} class LasySingleton {
/**
* 私有化构造方法,禁止外部直接new对象
*/
private LasySingleton() {
} /**
* 给予一个对象作为返回值使用
*/
private static LasySingleton instnace; /**
* 给予一个获取对象的入口
*
* @return LasySingleton对象
*/
public static LasySingleton getInstnace() {
if (null == instnace) {
instnace = new LasySingleton();
}
return instnace;
}
}
看起来很简单的样子,私有化构造方法,给予入口,返回对象,差不多就这样就可以了,但是有一个问题,如果是多线程呢?
public static LasySingleton getInstnace() {
if (null == instnace) {
instnace = new LasySingleton();
}
return instnace;
}
我们假想两个线程,要一起运行这段代码,线程A进来了,看到instnace是null的,ε=(´ο`*)))唉,线程B进来看见instnace也是null的(因为线程A还没有运行到instnace = new LasySingleton()这个代码),这时就会造成线程A,B创建了两个对象出来,也就不符合我们的单例模式了,我们来改一下代码。
public static LasySingleton getInstnace() {
if (null == instnace) {
synchronized (LasySingleton.class){
instnace = new LasySingleton();
}
}
return instnace;
}
这样貌似就可以了,就算是两个线程进来,也只有一个对象可以拿到synchronized锁,就不会产生new 两个对象的行为了,其实不然啊,我们还是两个线程来访问我们的这段代码,线程A和线程B,两个线程来了一看,对象是null的,需要创建啊,于是线程A拿到锁,开始创建,线程B继续等待,线程A创建完成,返回对象,将锁释放,这时线程B可以获取到锁(因为null == instnace判断已经通过了,在if里面进行的线程等待),这时线程B还是会创建一个对象的,这显然还是不符合我们的单例模式啊,我们来继续改造。
public static LasySingleton getInstnace() {
if (null == instnace) {
synchronized (LasySingleton.class){
if (null == instnace) {
instnace = new LasySingleton();
}
}
}
return instnace;
}
这次基本就可以了吧,回想一下我们上次的volatile有序性,难道真的这样就可以了吗?instnace = new LasySingleton()是一个原子操作吗?有时候你面试小厂,这样真的就可以了,我们来继续深挖一下代码。看一下程序的汇编指令码,首先找我们的class文件。运行javap -c ****.class。
E:\IdeaProjects\tuling-mvc-\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:
: aconst_null
: getstatic # // Field instnace:Lcom/tuling/control/LasySingleton;
: if_acmpne
: new # // class com/tuling/control/LasySingleton
: dup
: invokespecial # // Method "<init>":()V
: putstatic # // Field instnace:Lcom/tuling/control/LasySingleton;
: getstatic # // Field instnace:Lcom/tuling/control/LasySingleton;
: areturn
}
不是很好理解啊,我们只想看instnace = new LasySingleton()是不是一个原子操作,我们可以这样来做,创建一个最简单的类。
public class Demo {
public static void main(String[] args) {
Demo demo = new Demo();
}
}
然后我们运行javap -c -v ***.class
E:\IdeaProjects\tuling-mvc-\target\classes>javap -c -v Demo.class
Classfile /E:/IdeaProjects/tuling-mvc-/target/classes/Demo.class
Last modified --; size bytes
MD5 checksum f8b222a4559c4bf7ea05ef086bd3198c
Compiled from "Demo.java"
public class Demo
minor version:
major version:
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
# = Methodref #.# // java/lang/Object."<init>":()V
# = Class # // Demo
# = Methodref #.# // Demo."<init>":()V
# = Class # // java/lang/Object
# = Utf8 <init>
# = Utf8 ()V
# = Utf8 Code
# = Utf8 LineNumberTable
# = Utf8 LocalVariableTable
# = Utf8 this
# = Utf8 LDemo;
# = Utf8 main
# = Utf8 ([Ljava/lang/String;)V
# = Utf8 args
# = Utf8 [Ljava/lang/String;
# = Utf8 demo
# = Utf8 SourceFile
# = Utf8 Demo.java
# = NameAndType #:# // "<init>":()V
# = Utf8 Demo
# = Utf8 java/lang/Object
{
public Demo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=, locals=, args_size=
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: return
LineNumberTable:
line :
LocalVariableTable:
Start Length Slot Name Signature
this LDemo; public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=, locals=, args_size=
: new # // class Demo
: dup
: invokespecial # // Method "<init>":()V
: astore_1
: return
LineNumberTable:
line :
line :
LocalVariableTable:
Start Length Slot Name Signature
args [Ljava/lang/String;
demo LDemo;
}
SourceFile: "Demo.java" E:\IdeaProjects\tuling-mvc-\target\classes>
结果是这样的,我们来分析一下代码,先看这个
: new # // class Demo
就是什么意思呢?我们要给予Demo对象在对空间上开辟一个空间,并且返回内存地址,指向我们的操作数栈的Demo对象
: dup
是一个对象复制的过程。
: invokespecial # // Method "<init>":()V
见名知意,init是一个初始化过程,我们会把我们的刚才开辟的栈空间进行一个初始化,
: astore_1
这个就是一个赋值的过程,刚才我们有个复制的操作对吧,这时会把我们复制的一个对象赋值给我们的栈空间上的Demo,是不是有点蒙圈了,别急,后面的简单。
这是一个对象的初始化过程,在我的JVM系列博客简单的说过一点,后面我会详细的去说这个,总结起来就是三个过程。
.开辟空间
.初始化空间
.给引用赋值
这个代码一般情况下,会按照123的顺序去执行的,但是超高并发的场景下,可能会变为132,考虑一下是不是,我们的as-if-serial,132的执行顺序在单线程的场景下也是合理的,如果真的出现了132的情况,会造成什么后果呢?回到我们的单例模式,所以说我们上面单例模式代码还需要改。
public class LasySingletonMode {
public static void main(String[] args) {
LasySingleton instnace = LasySingleton.getInstnace();
}
} class LasySingleton { /**
* 私有化构造方法,禁止外部直接new对象
*/
private LasySingleton() {
} /**
* 给予一个对象作为返回值使用
*/
private static volatile LasySingleton instnace; /**
* 给予一个获取对象的入口
*
* @return LasySingleton对象
*/
public static LasySingleton getInstnace() {
if (null == instnace) {
synchronized (LasySingleton.class) {
if (null == instnace) {
instnace = new LasySingleton();
}
}
}
return instnace;
}
}
这样来写,就是一个满分的单例模式了,无论出于什么样的考虑,都是满足条件的。也说明你真的理解了我们的volatile关键字。
饿汉式相当于懒汉式就简单很多了,不需要考虑那么多了。
package com.tuling.control; public class HungrySingletonMode {
public static void main(String[] args) {
String name = HungrySingleton.name;
System.out.println(name);
}
} class HungrySingleton { /**
* 私有化构造方法,禁止外部直接new对象
*/
private HungrySingleton() {
} private static HungrySingleton instnace = new HungrySingleton(); public static String name = "XXX"; static{
System.out.println("我被创建了");
} public static HungrySingleton getInstance(){
return instnace;
}
}
很简单,也不是属于我们多线程范畴该说的,这里就是带着说了一下,就是当我们调用内部方法时,会主动触发对象的创建,这样就是饿汉模式。
java架构之路(多线程)大厂方式手写单例模式的更多相关文章
- java架构之路(多线程)JMM和volatile关键字(二)
貌似两个多月没写博客,不知道年前这段时间都去忙了什么. 好久以前写过一次和volatile相关的博客,感觉没写的那么深入吧,这次我们继续说我们的volatile关键字. 复习: 先来简单的复习一遍以前 ...
- [转帖]java架构之路-(面试篇)JVM虚拟机面试大全
java架构之路-(面试篇)JVM虚拟机面试大全 https://www.cnblogs.com/cxiaocai/p/11634918.html 下文连接比较多啊,都是我过整理的博客,很多答案都 ...
- java学习之路--多线程实现的方法
1 继承Thread类 继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Th ...
- java架构之路-(netty专题)初步认识BIO、NIO、AIO
本次我们主要来说一下我们的IO阻塞模型,只是不多,但是一定要理解,对于后面理解netty很重要的 IO模型精讲 IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式: ...
- Java多线程之Executor框架和手写简易的线程池
目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...
- Java面试必备:手写单例模式
面试官:请手写下几种常见的单例模式 我:好的(面带微笑),心里暗喜(送分题). 没成想提笔便写出了如此豪放的代码,不堪回首,请原谅我的不羁! 此篇整理了几种常见的单例模式代码示例,再有面试官让手撕单例 ...
- java面试之手写单例模式
为什么要有单例模式 实际编程应用场景中,有一些对象其实我们只需要一个,比如线程池对象.缓存.系统全局配置对象等.这样可以就保证一个在全局使用的类不被频繁地创建与销毁,节省系统资源. 实现单例模式的几个 ...
- 【Java】 ArrayList和LinkedList实现(简单手写)以及分析它们的区别
一.手写ArrayList public class ArrayList { private Object[] elementData; //底层数组 private int size; //数组大小 ...
- java架构之路-(Redis专题)聊聊大厂那些redis
上几次说了redis的主从,哨兵,集群配置,但是内部的选举一直没说,先来简单说一下选举吧. 集群选举 redis cluster节点间采取gossip协议进行通信,也就是说,在每一个节点间,无论主节点 ...
随机推荐
- Ubuntu16.04.3深度学习环境搭建
依赖 pip3 install pillow 安装numpy相关sudo apt-get install python-numpy python-scipy python-matplotlib ipy ...
- Python--day19--os模块
os模块 os模块是与操作系统交互的一个接口 os.makedirs('dirname1/dirname2') 可生成多层递归目录 os.removedirs('dirname1') 若目录为空,则删 ...
- codeforces 1230 div2
C 给一个图,并且在边上放上多米诺骨牌,使得每个多米诺骨牌指向的顶点的数字是一致的,并且每种骨牌只能用一种.问最多能够覆盖多少条边. 先生成每一个点指向的数字,然后判断就好了. #include< ...
- 在eclipse中建立lua开发环境
1. 给你的eclipse安装LuaEclipse,新增Eclipse Software Update Site“http://luaeclipse.luaforge.net/update-site ...
- Python--day24--多继承
如果本生没有func方法的话就调用距离自己最近的基类的方法 钻石继承: 查找方法的顺序:如下例的找func方法(广度优先) 例1: 例2: 漏斗继承: 小乌龟继承问题:(最顶端的节点F是最后查找的) ...
- Python--day46--MySQL自定义函数
1,mysql自定义函数(mysql函数会降低查找速度,使用 了函数的行和列不能再用索引查找了,这样对性能要求高的需求就不能这样写) 函数调用 select f(参数1,参数2): 对函数进行调用:
- [转]ASP.NET WebApi OWIN 实现 OAuth 2.0
OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. OAuth 允许用户提供一个令牌, ...
- C# 单例类
单例类 有时候我们不要在一个程序中创建太多的实例.只想用一个全局的实例和一个可以访问点.那么我们需要一个单例类. 因为是单例类啦,所以构造函数肯定是私有的. 需要了解的术语 懒汉式 顾名思义.什么时候 ...
- jieba—parallel
jieba 并行处理进行测试,注意:并行分词仅支持默认分词器 jieba.dt 和 jieba.posseg.dt import sys import time import jieba jieba. ...
- 【23.26%】【codeforces 747D】Winter Is Coming
time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...