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协议进行通信,也就是说,在每一个节点间,无论主节点 ...
随机推荐
- CSipsimple最新版本编译
CSipsimple最新版本编译 CSipsimple是依赖pjsip协议,官网http://code.google.com/p/csipsimple/,官网上也有已经编译好的apk,对于编译CSip ...
- hdu 1716 排序2(dfs)
排列2 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...
- Group_concat介绍与例子
进公司做的第一个项目就是做一个订单追踪查询,里里外外连接了十一个表,作为公司菜鸡的我麻了爪. 其中有一个需求就是对于多行的数据在一行显示,原谅我才疏学浅 无奈下找到了项目组长 在那学来了这个利器 ( ...
- [转载] 学会使用Web Service上(服务器端访问)~~~
http://www.360doc.com/content/10/0712/12/541242_38461863.shtml# 关于什么是Web Service,相信在很多地方都会有介绍.简单的讲,W ...
- caffe学习(1):多平台下安装配置caffe
如何在 centos 7.3 上安装 caffe 深度学习工具 有好多朋友在安装 caffe 时遇到不少问题.(看文章的朋友希望关心一下我的创业项目趣智思成) 今天测试并整理一下安装过程.我是在阿 ...
- Vue 小实例 跑马灯效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- H3C RIP路由表的更新
- js基础——面向对象(构造函数)
1.面向对象:类的标志,通过类可创建多个具有相同属性和方法的对象 2.创建对象 1)工厂模式方式:避免重复实例化但未能解决识别问题 function boss(name, age) { ...
- element-ui后台管理系统表单resetFields功能实现
项目中有‘新增’和‘编辑’弹出dialog功能,并且为同一个dialog. html代码: 新增时,这样的样式 编辑时,这样的样式 所以在编辑完关闭dialog后,需要清空表单,一开始简单的使用了el ...
- Educational Codeforces Round 54 (Rated for Div. 2) D Edge Deletion (SPFA + bfs)
题目大意:给定你一个包含n个点m条边的无向图,现在最多在图中保留k条边,问怎么删除多的边,使得图中良好的节点数最多,求出保留在图中的边的数量和编号. 良好的节点定义为:删除某条边后该点到点1的最短距离 ...