上期回顾:

  上次博客我们说了我们的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架构之路(多线程)大厂方式手写单例模式的更多相关文章

  1. java架构之路(多线程)JMM和volatile关键字(二)

    貌似两个多月没写博客,不知道年前这段时间都去忙了什么. 好久以前写过一次和volatile相关的博客,感觉没写的那么深入吧,这次我们继续说我们的volatile关键字. 复习: 先来简单的复习一遍以前 ...

  2. [转帖]java架构之路-(面试篇)JVM虚拟机面试大全

    java架构之路-(面试篇)JVM虚拟机面试大全 https://www.cnblogs.com/cxiaocai/p/11634918.html   下文连接比较多啊,都是我过整理的博客,很多答案都 ...

  3. java学习之路--多线程实现的方法

    1 继承Thread类 继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Th ...

  4. java架构之路-(netty专题)初步认识BIO、NIO、AIO

    本次我们主要来说一下我们的IO阻塞模型,只是不多,但是一定要理解,对于后面理解netty很重要的 IO模型精讲  IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式: ...

  5. Java多线程之Executor框架和手写简易的线程池

    目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...

  6. Java面试必备:手写单例模式

    面试官:请手写下几种常见的单例模式 我:好的(面带微笑),心里暗喜(送分题). 没成想提笔便写出了如此豪放的代码,不堪回首,请原谅我的不羁! 此篇整理了几种常见的单例模式代码示例,再有面试官让手撕单例 ...

  7. java面试之手写单例模式

    为什么要有单例模式 实际编程应用场景中,有一些对象其实我们只需要一个,比如线程池对象.缓存.系统全局配置对象等.这样可以就保证一个在全局使用的类不被频繁地创建与销毁,节省系统资源. 实现单例模式的几个 ...

  8. 【Java】 ArrayList和LinkedList实现(简单手写)以及分析它们的区别

    一.手写ArrayList public class ArrayList { private Object[] elementData; //底层数组 private int size; //数组大小 ...

  9. java架构之路-(Redis专题)聊聊大厂那些redis

    上几次说了redis的主从,哨兵,集群配置,但是内部的选举一直没说,先来简单说一下选举吧. 集群选举 redis cluster节点间采取gossip协议进行通信,也就是说,在每一个节点间,无论主节点 ...

随机推荐

  1. Ubuntu16.04.3深度学习环境搭建

    依赖 pip3 install pillow 安装numpy相关sudo apt-get install python-numpy python-scipy python-matplotlib ipy ...

  2. Python--day19--os模块

    os模块 os模块是与操作系统交互的一个接口 os.makedirs('dirname1/dirname2') 可生成多层递归目录 os.removedirs('dirname1') 若目录为空,则删 ...

  3. codeforces 1230 div2

    C 给一个图,并且在边上放上多米诺骨牌,使得每个多米诺骨牌指向的顶点的数字是一致的,并且每种骨牌只能用一种.问最多能够覆盖多少条边. 先生成每一个点指向的数字,然后判断就好了. #include< ...

  4. 在eclipse中建立lua开发环境

    1. 给你的eclipse安装LuaEclipse,新增Eclipse Software Update Site“http://luaeclipse.luaforge.net/update-site ...

  5. Python--day24--多继承

    如果本生没有func方法的话就调用距离自己最近的基类的方法 钻石继承: 查找方法的顺序:如下例的找func方法(广度优先) 例1: 例2: 漏斗继承: 小乌龟继承问题:(最顶端的节点F是最后查找的) ...

  6. Python--day46--MySQL自定义函数

    1,mysql自定义函数(mysql函数会降低查找速度,使用 了函数的行和列不能再用索引查找了,这样对性能要求高的需求就不能这样写) 函数调用 select f(参数1,参数2): 对函数进行调用:

  7. [转]ASP.NET WebApi OWIN 实现 OAuth 2.0

    OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. OAuth 允许用户提供一个令牌, ...

  8. C# 单例类

    单例类 有时候我们不要在一个程序中创建太多的实例.只想用一个全局的实例和一个可以访问点.那么我们需要一个单例类. 因为是单例类啦,所以构造函数肯定是私有的. 需要了解的术语 懒汉式 顾名思义.什么时候 ...

  9. jieba—parallel

    jieba 并行处理进行测试,注意:并行分词仅支持默认分词器 jieba.dt 和 jieba.posseg.dt import sys import time import jieba jieba. ...

  10. 【23.26%】【codeforces 747D】Winter Is Coming

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...