懒汉单例、静态内部类确保线程安全测试实例

This commit is contained in:
2018-09-24 13:43:44 +08:00
parent f975b3c9fe
commit c982cd4a7f
4 changed files with 117 additions and 12 deletions

View File

@@ -33,9 +33,10 @@ public class LazyDoubleCheckSingleton {
public static LazyDoubleCheckSingleton getInstance() { public static LazyDoubleCheckSingleton getInstance() {
// 首次检查 // 首次检查
if (lazySingleton == null) { if (lazySingleton == null) {
// 再次检查,在此处加锁比对方法加锁开销更小,因为如果首次检查不为空则直接返回
synchronized (LazyDoubleCheckSingleton.class) { synchronized (LazyDoubleCheckSingleton.class) {
log.debug("LazySingletonSynchronized实例化"); // 再次检查,在此处加锁比对方法加锁开销更小,因为如果首次检查不为空则直接返回
if (lazySingleton == null) {
log.debug("LazyDoubleCheckSingleton实例化");
lazySingleton = new LazyDoubleCheckSingleton(); lazySingleton = new LazyDoubleCheckSingleton();
// 实例化包含三个过程,这三个过程可能由于指令重排序导致执行顺序打乱 // 实例化包含三个过程,这三个过程可能由于指令重排序导致执行顺序打乱
// 但Java底层只保证三个过程在单线程内顺序一致 // 但Java底层只保证三个过程在单线程内顺序一致
@@ -44,6 +45,7 @@ public class LazyDoubleCheckSingleton {
// 3.LazyDoubleCheckSingleton对象指向刚分配的内存地址 // 3.LazyDoubleCheckSingleton对象指向刚分配的内存地址
} }
} }
}
return lazySingleton; return lazySingleton;
} }
@@ -57,10 +59,13 @@ public class LazyDoubleCheckSingleton {
// 使用线程等待模拟复杂实例化过程,让多线程同时进入该方法 // 使用线程等待模拟复杂实例化过程,让多线程同时进入该方法
Thread.sleep(1000); Thread.sleep(1000);
synchronized (LazyDoubleCheckSingleton.class) { synchronized (LazyDoubleCheckSingleton.class) {
log.debug("LazySingletonSynchronized实例化"); // 再次检查,在此处加锁比对方法加锁开销更小,因为如果首次检查不为空则直接返回
if (lazySingleton == null) {
log.debug("LazyDoubleCheckSingleton实例化");
lazySingleton = new LazyDoubleCheckSingleton(); lazySingleton = new LazyDoubleCheckSingleton();
} }
} }
}
return lazySingleton; return lazySingleton;
} }
} }

View File

@@ -0,0 +1,65 @@
package top.fjy8018.designpattern.pattern.creational.singleton;
import lombok.extern.slf4j.Slf4j;
/**
* 懒汉单例:静态内部类实现(线程安全)
* 使用静态内部类可以屏蔽类实例化的重排序,因为非构造线程不允许看到指令重排序
*
* @author F嘉阳
* @date 2018-09-24 13:11
*/
@Slf4j
public class StaticInnerClassSingleton {
/**
* 私有构造器是单例必须的
*/
private StaticInnerClassSingleton() {
}
/**
* 开发单例入口
*
* @return
*/
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
/**
* 静态内部类
* <p>
* 原理:
* <strong>JVM在类初始化时会加锁使用初始化锁可以同步多个线程对一个类的初始化确保了线程安全</strong>
* 类初始化时机
* 一. 主动引用
* 虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生):
* 1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。
* 最常见的生成这 4 条指令的场景是:
* (1) 使用 new 关键字实例化对象的时候;
* (2) 读取或赋值一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;
* (3) 以及调用一个类的静态方法的时候。
* (4) 使用一个类的非常量静态成员
* (5) 若一个类是顶级类,且该类有嵌套的断言语句(少见)
* <p>
* 2. 使用 {@link java.lang.reflect} 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
* 3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
* 4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
* 5. 当使用 JDK 1.7 的动态语言支持时,如果一个 {@link java.lang.invoke.MethodHandle}
* 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
* <p>
* 二. 被动引用
* 以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
* <p>
* 1. 通过子类引用父类的静态字段,不会导致子类初始化。
* System.out.println(SubClass.value); // value 字段在 SuperClass 中定义
* 2. 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 {@link Object} 的子类,其中包含了数组的属性和方法。
* SuperClass[] sca = new SuperClass[10];
* 3. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
* System.out.println(ConstClass.HELLOWORLD);
*/
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
}

View File

@@ -28,9 +28,9 @@ class LazyDoubleCheckSingletonTest {
private class MyRunnable implements Runnable { private class MyRunnable implements Runnable {
@Override @Override
public void run() { public void run() {
LazySingletonSynchronized lazySingleton = null; LazyDoubleCheckSingleton lazySingleton = null;
try { try {
lazySingleton = LazySingletonSynchronized.getInstance2(); lazySingleton = LazyDoubleCheckSingleton.getInstance2();
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@@ -0,0 +1,35 @@
package top.fjy8018.designpattern.pattern.creational.singleton;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
class StaticInnerClassSingletonTest {
@Test
void getInstance() throws InterruptedException {
Thread thread1 = new Thread(new StaticInnerClassSingletonTest.MyRunnable());
Thread thread2 = new Thread(new StaticInnerClassSingletonTest.MyRunnable());
thread1.start();
thread2.start();
Thread.sleep(500);
log.debug("finish");
}
private class MyRunnable implements Runnable {
@Override
public void run() {
StaticInnerClassSingleton lazySingleton = null;
try {
lazySingleton = StaticInnerClassSingleton.getInstance();
} catch (Exception e) {
e.printStackTrace();
}
log.info(Thread.currentThread().getName() + " " + lazySingleton);
}
}
}