龙江网站设计,原单手表网站,wordpress怎么用vue,东莞网站建设 汇卓目录 背景前置知识类加载运行全过程 单例模式的实现方式一、饿汉式基本介绍源码分析 二、懒汉式基本介绍源码分析改进 三、懒汉式单例终极解决方案#xff08;静态内部类#xff09;#xff08;推荐使用方案#xff09;基本介绍源码分析 感谢 背景
最近学习了JVM之后… 目录 背景前置知识类加载运行全过程 单例模式的实现方式一、饿汉式基本介绍源码分析 二、懒汉式基本介绍源码分析改进 三、懒汉式单例终极解决方案静态内部类推荐使用方案基本介绍源码分析 感谢 背景
最近学习了JVM之后总感觉知识掌握不够深所以想通过分析经典的【懒汉式单例】来加深一下理解。主要是【静态内部类】实现单例的方式。 如果小白想理解单例的话也能看我这篇文章。我也通过了【前置知识】跟【普通懒汉式】、【双检锁懒汉】、【静态内部类】懒汉给大家分析了一下他们的线程安全性。但是我这边没有完整的演进【懒汉式单例】历程。所以会缺少思维上的递进。不过我在最后的【感谢】名单里提供了一个完整的【懒汉式单例演进】的链接建议可以结合这个文章一起学习。
前置知识
类加载运行全过程
当我们用java命令运行某个类的main函数启动程序时首先需要通过类加载器把主类加载到JVM。
package com.tuling.jvm;public class Math {public static final int initData 666;public static User user new User();public int compute() { //一个方法对应一块栈帧内存区域int a 1;int b 2;int c (a b) * 10;return c;}public static void main(String[] args) {Math math new Math();math.compute();}
}通过Java命令执行代码的大体流程如下 其中loadClass的类加载过程有如下几步 加载 验证 准备 解析 初始化 使用 卸载
加载在硬盘上查找并通过IO读入字节码文件使用到类时才会加载例如调用类的main()方法new对象等等在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象作为方法区这个类的各种数据的访问入口验证校验字节码文件的正确性准备给类的静态变量分配内存并赋予默认值解析将符号引用替换为直接引用该阶段会把一些静态方法(符号引用比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用)这是所谓的静态链接过程(类加载期间完成)动态链接是在程序运行期间完成的将符号引用替换为直接引用下节课会讲到动态链接初始化对类的静态变量初始化为指定的值执行静态代码块
总结一下上面说的加载 验证 准备 解析 初始化过程是由JVM帮我们进行的所以对我们程序员来说【天生】就具备线程安全性这个由JVM帮我们保证无需我们关心。
单例模式的实现方式
单例模式是我们Java中很常见的一个设计模式。所以有这么一种说法遇事不决单例解决。 Java单例通常有2种分别为饿汉式、懒汉式
一、饿汉式
基本介绍
饿汉式Eager Initialization急切的初始化在类加载时就创建单例实例并在需要时直接返回该实例。这种方式的实现是线程安全的因为在类加载过程中实例已经创建好了。
源码
public class SingletonTest {private static final SingletonTest me new SingletonTest();public static SingletonTest me() {return me;}public static void main(String[] args) {System.out.println(SingletonTest.me());System.out.println(SingletonTest.me());System.out.println(SingletonTest.me());}
// 系统输出如下
// org.tuling.juc.singleton.SingletonTest12a3a380
// org.tuling.juc.singleton.SingletonTest12a3a380
// org.tuling.juc.singleton.SingletonTest12a3a380
}分析
因为单例对象SingletonTest 是静态成员变量所以在JVM类加载过程中加载-》验证-》准备-》解析-》初始化的【解析】阶段已经被JVM初始化了所以由JVM保证了线程安全性。
二、懒汉式
基本介绍
懒汉式Lazy Initialization在首次调用时创建单例实例存在线程安全问题。如果多个线程同时进入判断条件可能会创建多个实例。
源码
public class SingletonTest {private static SingletonTest me;public static SingletonTest me() {if(me null) {me new SingletonTest();}return me;}public static void main(String[] args) {for (int i 0; i 10; i) {new Thread(()-{System.out.println(SingletonTest.me());}).start();}}
}输出结果如下
分析
为什么上面这段代码不是线程安全的呢我们举一个极端的例子如下图所示 在没有锁机制的存在情况下多线程环境里面可能会出现上述的并发执行情况。在线程1判断完me null之后即将开始执行new之前线程2也刚好在判断me null这是因为线程1还没有执行new操作所以线程2判断肯定是null的于是也开始new。这就是线程安全问题所在。 PS小白们一定要理解上面这个图。虽然很简单但是说它是你们迈向或者培养【并发意识】的启蒙都不为过。
改进
为了解决上面的问题大牛们进行了改进使用了【双检锁volatile】机制【双检锁】即双重检查锁。代码如下
public class SingletonTest {private static volatile SingletonTest me;public static SingletonTest me() {if(me null) {synchronized (SingletonTest.class) {if (me null) {me new SingletonTest();}}}return me;}
}上面的改进关键点如下
使用了volatile关键字修饰单例对象me在获取单例对象的时候判断了两次if(me null)第二次判断if(me null)之前先加了锁
第二、三点我就不说了大家可以看看最下面【感谢】的友链。这里重点说说第一点。 估计小白会很难理解为什么一定要volatile关键字修饰不用可以吗答案是不可以。因为volatile能禁止重排序。什么是【重排序呢】说的简单点就是JVM甚至是CPU为了性能可能会在不改变语义的情况下修改我们的代码执行顺序。比如当我们new SingletonTest()的时候你以为只有一步操作实际上它有3步如下 memory allocate(); // 1.分配对象内存空间 instance(memory); // 2.初始化对象 instance memory; // 3.设置instance指向刚分配的内存地址此时instancenull 但事实上经过重排序之后可能会变成下面的执行顺序 memory allocate(); // 1.分配对象内存空间 instance memory; // 3.设置instance指向刚分配的内存地址此时instancenull instance(memory); // 2.初始化对象 然后大家再用上面的【并发启蒙】意识自己画个图看下还能线程安全吗 所以需要使用volatile关键字告诉底层JVM或者CPU不要帮我重排序这个对象于是就避免了上面的并发线程安全问题了。
三、懒汉式单例终极解决方案静态内部类推荐使用方案
基本介绍
这里通过利用JVM类加载【天生线程安全】的特性来帮助实现【懒汉式】的单例。如何做到呢答案是【静态内部类】。
源码
public class SingletonTest {/** 单例对象可以直接调用配置属性 */private static class Holder {private static SingletonTest me new SingletonTest();}public static SingletonTest me() {return Holder.me;}public static void main(String[] args) {int threadCount 10000;for (int i 0; i threadCount; i) {new Thread(()-{System.out.println(SingletonTest.me());}).start();}}
}上面的代码新建了1W个线程来调用单例我们发现结果都是一样同一个对象。
分析
为什么上面通过静态内部类能保证线程安全性呢这个我们在【前置知识】已经说过了是由JVM保证了线程安全性。 如上图所示只有当我们使用了SingletonTest.me()的时候才会去开始加载Holder静态内部类这就是它实现【懒汉式】的原因延迟加载。
感谢
感谢【作者weixin_47196090】的深度好文《懒汉式单例演进到DCL懒汉式 深度全面解析》