单例模式的应用场景
单例模式 重点在保证 一个类在任何情况下都只有一个 实例。例举生活中的例子,如公司 的CEO 只有一个,我的❤只有一个,spring容器只有一个,朕的后宫只有一个皇后。
饿汉模式
第一种写法:
package com.singleton.pattern.singleton.demo;
/**
* @Classname HungrySingleton
* @Description 饿汉模式
* @Date 2019/11/20 0020 15:23
* @Created by 埔枘
*/
public class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
/**
* 私有化 空构造
*/
private HungrySingleton(){}
public static HungrySingleton getSingleton(){
return hungrySingleton;
}
}
第二种写法:
package com.singleton.pattern.singleton.demo;
/**
* @Classname HungrySingleton
* @Description 饿汉模式(静态加载)
* @Date 2019/11/20 0020 15:23
* @Created by 埔枘
*/
public class HungrySingleton2 {
private static final HungrySingleton2 hungrySingleton;
static{
hungrySingleton = new HungrySingleton2();
}
/**
* 私有化 空构造
*/
private HungrySingleton2(){}
public static HungrySingleton2 getSingleton(){
return hungrySingleton;
}
}
以上两种写法 在容器加载时 创了对象。
饿汉式适用在单例对象较少的情况。(单例对象多的情况下,在初始化时加载过多的对象,影响性能。)
懒汉模式
单线程情况下以下代码没什么问题,但是换成多线程试试?
package com.singleton.pattern.singleton.demo;
/**
* @Classname LazySingleton
* @Description 懒汉模式(即懒加载)
* @Date 2019/11/20 0020 15:30
* @Created by 埔枘
*/
public class LazySingleton {
private LazySingleton(){}
public static LazySingleton lazySingleton = null;
/**
* 外部调用时 才会加载
* @return
*/
public static LazySingleton getSingleton(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
新建一个线程类,并在里面获取了 懒加载的单例,打印当前线程及获取的单例对象。
package com.singleton.pattern.singleton.demo;
/**
* @Classname ExectorThread
* @Description TODO
* @Date 2019/11/20 0020 15:36
* @Created by 埔枘
*/
public class ExectorThread implements Runnable{
@Override
public void run() {
LazySingleton singleton = LazySingleton.getSingleton();
System.out.println(Thread.currentThread().getId()+"---"+Thread.currentThread().getName()+"----"+singleton);
}
}
情况如下 获取到了不一样的对象,意味着 这里存在线程的安全问题

在通过 synchronized 关键字同步,解决线程安全问题

我们来 多线程 debug 下
设置多线程 debug


当 Thread-0 执行完后 发现 Thread-1 进入了 RUNNING 状态


但是???但是???但是???但是???但是???但是???但是???但是???但是???但是???但是?
在线程数量过多的情况下 synchronized 会使 CPU 的压力增大,那么有没有更好的办法来解决这个问题呢?
双重检查锁单例
**双重检查,减少触发 synchronized 同步的操作,提高性能。
既节约了空间又保证了线程的安全性,但是 由于jvm 的 存在乱序执行功能,还是会导致 线程安全性问题,造成不可靠的原因是编译器为了提高执行效率的指令重排。只要认为在单线程下是没问题的,它就可以进行乱序写入!以保证不要让cpu指令流水线中断,这个就是著名的DCL失效问题。**
先说一个概念:
new LazyDoubleCheckSingleton()
new 一个对象,
1.第一步会先在堆内存中开辟一个空间
2.初始化对象 在 堆空间
3.把栈内存中的引用指向 第一步开辟的空间地址
以上步骤(只要认为在单线程下是没问题的) jvm 乱序执行 会导致 步骤变为 1,3,2
举例:
线程A,线程B
线程A jvm 乱序执行导致 对象还没初始化 就把引用指向了 堆中的地址,然后线程B 进来判断
A:标识 处(看下面代码) 不为空 就直接返回了 就会导致异常。
ps:在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile

package com.singleton.pattern.singleton.demo;
/**
* @Classname LazySingleton
* @Description 双重检查锁单例
* @Date 2019/11/20 0020 15:30
* @Created by 埔枘
*/
public class LazyDoubleCheckSingleton {
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton lazySingleton = null;
/**
* 外部调用时 才会加载
* @return
*/
public static synchronized LazyDoubleCheckSingleton getSingleton(){
if(lazySingleton == null){ A:标识
synchronized (LazyDoubleCheckSingleton.class){
if(lazySingleton == null){
lazySingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazySingleton;
}
}
那么怎么解决 jvm 乱序带来的问题呢?
方案:
1.使用volatile 关键字 禁止jvm 重排序
package com.singleton.pattern.singleton.demo;
/**
* @Classname LazySingleton
* @Description 双重检查锁单例
* @Date 2019/11/20 0020 15:30
* @Created by 埔枘
*/
public class LazyDoubleCheckSingleton {
private LazyDoubleCheckSingleton(){}
public static volatile LazyDoubleCheckSingleton lazySingleton = null;
/**
* 外部调用时 才会加载
* @return
*/
public static LazyDoubleCheckSingleton getSingleton(){
if(lazySingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazySingleton == null){
lazySingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazySingleton;
}
}
2.屏蔽 多线程之间的 乱序可视化( 使线程A 的乱序 在线程B 中看不到)
从下面的ClassLoader中可以看出 只有 jvm 加载类 时才会是 一个线程 去加载类
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
所以最好的方式是 在jvm 加载时去加载这个类,但是不能使用 饿汉模式。
ps:只要用到了 synchronized 锁 则一定会影响到性能。
往下看

单例-静态内部类
静态内部类的加载时机在 getSingleton() 第一次执行时
内部类会在 方法调用之钱初始化 巧妙的避开了 线程安全问题。
ps:此模式兼顾了 饿汉的内存浪费 和 synchronized 的性能问题
package com.singleton.pattern.singleton.demo;
/**
* @Classname LazyInnerClassSingleton
* @Description 单例 静态内部类实现
* @Date 2019/11/20 0020 17:03
* @Created by 埔枘
*/
public class LazyInnerClassSingleton {
private LazyInnerClassSingleton(){}
public static LazyInnerClassSingleton lazySingleton = null;
/**
* final 关键字 保证方法不会被 重写和重载
* @return
*/
public static final LazyInnerClassSingleton getSingleton(){
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder {
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
注册式单例
注册式单例:
描述:注册式单例 又叫登记式的单例,就是把实例登记到 一个地方,然后通过 一个唯一标识 获取。实现方式分为如下两种:
1.枚举单例
2.容器单例
枚举单例
package com.singleton.pattern.singleton.demo;
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
容器式单例
ps:容器式写法适用于 实力非常多的情况。
package com.singleton.pattern.singleton.demo;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 容器式写法适用于 实例非常多的情况
*/
public class ContainerSingleton {
/**
* 私有化构造
*/
private ContainerSingleton(){}
/**
* 容器
*/
private static Map<String,Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//锁 ioc
synchronized (ioc){
if(!ioc.containsKey(className)){
Class<?> aClass = Class.forName(className);
Object newInstance = aClass.newInstance();
ioc.put(className,newInstance);
return newInstance;
}else{
return ioc.get(className);
}
}
}
}
ThreadLocal 单例
这是一个伪单例,它并不能实现全局的实例唯一,只能实现在某个线程中的实例唯一,并且天生式线程安全的。
package com.singleton.pattern.singleton.demo;
/**
* 在 某个线程中的 单例 (伪单例)
* 并且 天生是线程安全的
*/
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstanceThreadLocal
= new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){
}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstanceThreadLocal.get();
}
}
总结
饿汉单例(预先加载):
1.静态属性实现
2.静态代码块实现
懒汉单例(懒加载):
1.正常懒汉单例( 需要加synchronized,但影响性能 )
2.双重检查单例( 第一层非 null 判断 减少 synchronized 锁的使用,提高性能,JVM乱序问题)
3.静态内部类
注册式单例:
1.枚举单例(推荐使用)
2.容器单例
伪单例:
1.ThreadLocal单例
参考:
下期介绍如何破坏单例