【项目实战】多线程环境下正确创建单例
来源:互联网 发布:淘宝 prd mrd 模板 编辑:程序博客网 时间:2024/05/22 04:57
前言
对项目代码进行扫描时,出现静态扫描严重问题,发现是由于多线程环境下没有正确创建单例所导致。
问题分析
本项目使用的
JDK 1.7+
。
项目代码如下(修改了类名,但核心没变)
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; }}
按照项目生成单例代码,使用如下测试类进行测试
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; } }}
输出结果如下:
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4
从结果看,都生成了同一个实例,似乎不存在问题,多线程环境下确实不太好重现问题,现改动代码如下:
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in outer if block"); synchronized (mutexObj) { System.out.println(Thread.currentThread().getName() + " in synchronized block"); if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in inner if block"); tmp = new Singleton(); cache = tmp; } System.out.println(Thread.currentThread().getName() + " out inner if block"); } System.out.println(Thread.currentThread().getName() + " out synchronized block"); } System.out.println(Thread.currentThread().getName() + " out outer if block"); return cache; }}
上述代码中添加了Thread.sleep(1)
这条语句,其中,Thread.sleep(1)
进行休眠时,线程不会释放拥有的锁,并且打印了相关的语句,便于查看线程正运行在哪里的状态。
再次测试,输出结果如下:
Thread2 in outer if block
Thread1 in outer if block
Thread0 in outer if block
Thread2 in synchronized block
Thread2 in inner if block
Thread2 out inner if block
Thread2 out synchronized block
Thread0 in synchronized block
Thread2 out outer if block
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@60b07af1
Thread0 in inner if block
Thread0 out inner if block
Thread0 out synchronized block
Thread1 in synchronized block
Thread0 out outer if block
Thread1 in inner if block
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@625795ce
Thread1 out inner if block
Thread1 out synchronized block
Thread1 out outer if block
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@642c39d2
从结果看,生成了3个不同的实例,并且每个线程都执行了完整的流程,并且可知单例的创建存在问题。在分析原因前简单了解下多线程模型,多线程模型如下:
每个线程有自己独立的工作空间,线程间进行通信是通过主内存完成的,想了解详细内容可参见如下链接:内存模型或深入理解java内存模型。
知道每个线程会有一份tmp拷贝后,配合打印输出,就不难分析出原因。
问题解决
按照《Effective Java》一书中创建单例的推荐,可使用如下两种解决方法
双重锁检查机制
需要配合
volatile
关键字使用,并且需要JDK
版本在1.5
以上,核心代码如下
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { tmp = cache; synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; }}
进行如下测试(添加打印语句方便分析):
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in outer if block"); synchronized (mutexObj) { System.out.println(Thread.currentThread().getName() + " in synchronized block"); tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in inner if block"); tmp = new Singleton(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } cache = tmp; } System.out.println(Thread.currentThread().getName() + " out inner if block"); } System.out.println(Thread.currentThread().getName() + " out synchronized block"); } System.out.println(Thread.currentThread().getName() + " out outer if block"); return tmp; } }}
输出结果如下:
Thread0 in outer if block
Thread0 in synchronized block
Thread0 in inner if block
Thread2 in outer if block
Thread1 in outer if block
Thread0 out inner if block
Thread0 out synchronized block
Thread1 in synchronized block
Thread1 out inner if block
Thread1 out synchronized block
Thread1 out outer if block
Thread0 out outer if block
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
Thread2 in synchronized block
Thread2 out inner if block
Thread2 out synchronized block
Thread2 out outer if block
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
从结果中和线程运行步骤可以看到三个线程并发的情况下,只生成了唯一实例。
静态内部类
无
JDK
版本限制,也不需要使用volatile
关键字即可完成单例模式,核心代码如下:
static class Singleton { private Singleton() { } private static class InstanceHolder { public static Singleton instance = new Singleton(); } public static Singleton getInstance() { return InstanceHolder.instance; }}
进行如下测试:
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private Singleton() { } private static class InstanceHolder { public static Singleton instance = new Singleton(); } public static Singleton getInstance() { return InstanceHolder.instance; } }}
运行结果如下:
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
该模式可保证使用时才会初始化变量,达到延迟初始化目的。
总结
单例模式在多线程环境下不太好编写,并且不容易重现异常,编写时需要谨慎,在项目中遇到问题也需要多总结和记录。
参考文档
- 双重检查锁定与延迟初始化
- Effective Java
- 深入理解Java内存模型
- 【项目实战】多线程环境下正确创建单例
- JAVA多线程下创建单例的正确方式
- 多线程环境下的单例实现
- 多线程情况下的单例模式创建
- c++单例模式在多线程环境下的安全性
- 多线程环境下使用的单例模式的实现
- 单例的多线程环境下的使用
- Java设计模式(二):单例模式的5种实现方式,以及在多线程环境下5种创建单例模式的效率
- Java设计模式(二):单例模式的5种实现方式,以及在多线程环境下5种创建单例模式的效率
- 单例模式的5种实现方式,以及在多线程环境下5种创建单例模式的效率
- 单例模式:单线程和多线程并发情况下的对象创建
- FCGI单线程环境和多线程环境下的例子
- FCGI单线程环境和多线程环境下的例子
- 多线程下的单例
- 多线程下的单例
- 多线程下的单例
- 多线程下的单例
- java多线程中正确的单例模式
- JAVA学习笔记_StringUtil.isEmpty_"null"不是null
- Linux下如何编译并运行C程序
- 利用Spring的propertyConfigurer类 读取.property数据库配置文件
- datebox默认显示为上个月最后一天
- C语言第一课
- 【项目实战】多线程环境下正确创建单例
- Samba
- 基于Springmvc的web应用的跨域实现
- CSS包含块(the containing block)和与之相关的百分比属性
- mybatis调用oracle自定义函数
- KMP中的一些技巧(°ο°)~ @
- [My SQL] 创建表和操纵表
- 【Android 多媒体开发】 MediaPlayer 状态机 接口 方法 解析
- 一步一步教你centos7.3安装redis