参考资料:

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. 跳石头(codevs 4768)

    题目描述 Description 一年一度的“跳石头”比赛又要开始了! 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有N ...

  2. hdu3622:Bomb Game

    给n<=100对点,从每对点里面挑一个并以这些挑出的点为圆心画圆,并且这些圆不能互相覆盖,找出一种方案使得这些圆半径中最小的那个最大. “最小值最大”就是二分答案啦!考虑现在每个点都画出半径x的 ...

  3. Notification通知创建

    Notification通知创建 由于通知是一个远程视图,所以创建通知在状态栏显示需要用到三个主要的对象: 一.PendingIntent对象,用来承载Intent对象的,Intent对象主要是定义通 ...

  4. Heredoc和Nowdoc

    就象heredoc结构类似于双引号字符串,Nowdoc结构是类似于单引号字符串的.Nowdoc结构很象heredoc结构,但是 nowdoc不进行解析操作 . 这种结构很适合用在不需要进行转义的PHP ...

  5. C. The Two Routes---cf602C(Dij)

    http://codeforces.com/problemset/problem/602/C 题目大意:  有n个城市 有m条铁路  如果两个城市没有铁路  那么一定有公路 求从1 到 n 用铁路和公 ...

  6. 几道hash题

    1: UVa 10887 - Concatenation of Languages map 可以做 ,但是输入实在恶心,有空串之类的HASH模板: int Hash(char *s){   int s ...

  7. java内存区域和对象的产生

    一直被java中内存组成弄的头晕眼花,这里总结下都有哪些,先上图片 程序计数器 小块内存,线程执行字节码的信号指示器,以此获取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复都要依赖他 ...

  8. Ubuntu 16.04安装QQ(不一定成功)

    注意1:如果是刚新装的系统,可以正常安装,但是,如果你已经装了很多软件,千万不要安装,因为会把系统上一般的依赖包和你之前装的软件全部卸载掉!甚至将桌面Dock都会卸载!最终只能重装Ubuntu解决. ...

  9. 重装JDK后Tomcat和Eclipse的配置

    比如JDK之前是1.8.0_31的,升级之后变成了1.8.0_131之后,Tomcat需要做如下配置: 对于Eclipse中之前配置的Tomcat需要删除后重新添加一个.

  10. SVN 更新后Tomcat 启动时出现问题

    更新了svn后,启动本地的tomcat容器发现启动不了, 检查后发现是配置文件配置出现问题,因为我们的该项目 配置有tomcat与weblogic两种 , 而在我的web.xml中 默认配置的serv ...