参考资料:

http://ifeve.com/java-memory-model-4/

http://www.infoq.com/cn/articles/java-memory-model-1

http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/

https://en.wikipedia.org/wiki/Singleton_pattern#Java_5_solution

https://www.ibm.com/developerworks/java/library/j-jtp06197/

1. volatile

final class Singleton {
private static Singleton instance = null; private Singleton() {
} public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

以上代码尝试实现单例模式,但存在严重的线程安全风险。Java Memory Model定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。假设Thread1/Thread2并发,instance为它们的共享变量,Thread1与Thread2之间通信必须要经历下面2个步骤:

  • Thread1把本地内存更新过的instance刷新到主内存中去
  • Thread2到主内存中去读取Thread1之前已更新过的instance

那么可能的场景之一——Thread1执行完instance = new Singleton(),但刷新到主内存前Thread2的instance == null仍然成立,于是再次执行instance = new Singleton(),这时两个线程得到了两个不同的对象,与预期不符。

final class Singleton {
private static Singleton instance = null; private Singleton() {
} public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

加入锁和双重校验后,仍然存在风险,因为为了提高性能,编译器和处理器常常会对指令做重排序,以Singleton instance = new Singleton()为例,它包含了三个指令:

  • ①为instance分配内存
  • ②调用Singleton构造方法
  • ③把instance指向分配的内存地址

三个指令执行顺序可能是①②③或①③②,在③执行之后,instance==null将不再成立。可能的场景——假设Thread1/Thread2并发,Thread1执行了除②以外的指令,Thread2的instance==null不成立,虽然得到了内存地址,但由于未调用构造方法而报错。

final class Singleton {
private static volatile Singleton instance = null; private Singleton() {
} public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

为instance变量加上volatile关键字彻底解决问题。volatile的特性:

  • volatile的变量修改后将立即刷新到主内存,其他线程即可读取到新值
  • 编译器利用内存屏障的概念禁止上述三条指令的重排序,只允许①②③的执行顺序

由于以上特性使volatile极适用于修饰多线程环境下的状态标识。

2. ThreadLocal

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

以非线程安全的SimpleDateFormat类为例,在并发运行时会出错,但使用ThreadLocal维护则可以完美避免此问题

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; /**
* @Description: 测试ThreadLocal
*/
public class ThreadLocalTest {
private static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
private static final ThreadLocal<DateFormat> DATE_FORMAT = new ThreadLocal<DateFormat>() {
public DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
}; public static void main(String[] args) throws InterruptedException {
String date = "2017-07-06";
testDateFormat(date);
testThreadLocal(date);
} private static void testDateFormat(String date) throws InterruptedException {
multilpleThreadExecute(new Runnable() {
@Override
public void run() {
try {
System.out.println(df.parse(date));
} catch (ParseException e) {
}
}
});
} private static void testThreadLocal(String date) throws InterruptedException {
multilpleThreadExecute(new Runnable() {
@Override
public void run() {
try {
System.out.println(DATE_FORMAT.get().parse(date));
} catch (ParseException e) {
}
}
});
} private static void multilpleThreadExecute(Runnable runnable) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
executorService.execute(runnable);
}
executorService.shutdown();
executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
}
}

Java多线程系列八——volatile和ThreadLocal的更多相关文章

  1. java多线程系列(八)---CountDownLatch和CyclicBarrie

    CountDownLatch 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线 ...

  2. 【Java多线程系列八】volatile和ThreadLocal

    1. volatile final class Singleton { private static Singleton instance = null; private Singleton() { ...

  3. java多线程系列(九)---ArrayBlockingQueue源码分析

    java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...

  4. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  5. Java多线程系列--“JUC锁”08之 共享锁和ReentrantReadWriteLock

    概要 Java的JUC(java.util.concurrent)包中的锁包括"独占锁"和"共享锁".在“Java多线程系列--“JUC锁”02之 互斥锁Ree ...

  6. Java多线程系列--“JUC线程池”03之 线程池原理(二)

    概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...

  7. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  8. Java多线程系列——从菜鸟到入门

    持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...

  9. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

随机推荐

  1. 获取当前日期的T-SQL语句

    CONVERT(nvarchar(10),count_time,121): CONVERT为日期转换函数,一般就是在时间类型 (datetime,smalldatetime)与字符串类型(nchar, ...

  2. mapStruct笔记

    背景 mapStruct 是一个方便对象转换的工具,类似的工具还有 Dozer, BeanUtils. 实现 mapStruct的核心是在编译期生成基于转换规则的 Impl 文件,运行时直接调用 Im ...

  3. 使用WaveOut API播放WAV音频文件(解决卡顿)

    虽然waveout已经过时,但是其api简单,有些时候也还是需要用到. 其实还是自己上msdn查阅相应api最靠谱,waveout也有提供暂停.设置音量等接口的,这里给个链接,需要的可以自己查找: h ...

  4. hdu4115:Eliminate the Conflict

    n<=10000局剪刀石头布,对面第i局出Ai,m<=10000种对你出什么提出的要求:Xi Yi Wi 表示第Xi局和第Yi局,Wi=1:必须不同:Wi=0:必须相同,问是否存在你一局都 ...

  5. 新建一个基于vue.js+Mint UI的项目

    上篇文章里面讲到如何新建一个基于vue,js的项目(详细文章请戳用Vue创建一个新的项目). 该项目如果需要组件等都需要自己去写,今天就学习一下如何新建一个基于vue.js+Mint UI的项目,直接 ...

  6. 转 蓝桥杯 历届试题 波动数列 [ dp ]

    传送门   历届试题 波动数列   时间限制:1.0s   内存限制:256.0MB     锦囊1   锦囊2   锦囊3   问题描述 观察这个数列: 1 3 0 2 -1 1 -2 ... 这个 ...

  7. UINavigationController 小记

    1.以栈的形式管理视图控制器,push 和 pop 方法来弹入和弹出控制器,最多只能显示一个视图控制器. 2.使用pop方法可以移除栈顶控制器,当一个控制器被pop后,控制器内存会被释放了. 3.一层 ...

  8. linux文件描述符fd(windows下的句柄)

    在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件 fd:file descriptor 文件描述符0,1,2分别给了标准输入.标准输出和错误输出. ls - ...

  9. 基于unicorn-engine的虚拟机的实现(WxSpectre)

    反病毒虚拟机是一个很有优势的工具,可以说反病毒软件是否存在模拟器是衡量反病毒软件能力的一个指标.反病毒虚拟机不光是内嵌在反病毒软件内部,来动态执行样本.这种虚拟机一般也可以单独用来动态执行批量样本,检 ...

  10. 【stl学习笔记】vector

    vector是定义于namespace std内的template: namespace std { template<class T, class Allocator = allocator& ...