【Java多线程系列八】volatile和ThreadLocal
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维护则可以完美避免此问题
package com.concurrent.test; import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; 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(final String date) throws InterruptedException {
multilpleThreadExecute(new Runnable() {
@Override
public void run() {
try {
System.out.println(df.parse(date));
} catch (Exception e) {
e.printStackTrace();
}
}
});
} private static void testThreadLocal(final String date) throws InterruptedException {
multilpleThreadExecute(new Runnable() {
@Override
public void run() {
try {
System.out.println(DATE_FORMAT.get().parse(date));
} catch (Exception e) {
e.printStackTrace();
}
}
});
} 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的更多相关文章
- Java多线程系列八——volatile和ThreadLocal
参考资料: http://ifeve.com/java-memory-model-4/ http://www.infoq.com/cn/articles/java-memory-model-1 htt ...
- java多线程系列(八)---CountDownLatch和CyclicBarrie
CountDownLatch 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线 ...
- java多线程系列(九)---ArrayBlockingQueue源码分析
java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- Java多线程系列--“JUC锁”08之 共享锁和ReentrantReadWriteLock
概要 Java的JUC(java.util.concurrent)包中的锁包括"独占锁"和"共享锁".在“Java多线程系列--“JUC锁”02之 互斥锁Ree ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- java多线程系列(三)---等待通知机制
等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...
- Java多线程系列——从菜鸟到入门
持续更新系列. 参考自Java多线程系列目录(共43篇).<Java并发编程实战>.<实战Java高并发程序设计>.<Java并发编程的艺术>. 基础 Java多线 ...
- Java多线程系列--“JUC锁”03之 公平锁(一)
概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...
随机推荐
- token是什么?和session什么区别,怎么用
对于初学者来说,对Token和Session的使用难免会限于困境,开发过程中知道有这个东西,但却不知道为什么要用他?更不知道其原理,今天我就带大家一起分析分析这东西. 一.我们先解释一下他的含义: 1 ...
- java中的fail-fast(快速失败)机制
java中的fail-fast(快速失败)机制 简介 fail-fast机制,即快速失败机制,是java集合中的一种错误检测机制.当在迭代集合的过程中对该集合的结构改变是,就有可能会发生fail-fa ...
- FZU 2059 MM
Description There is a array contain N(1<N<=100000) numbers. Now give you M(1<M<10000) q ...
- C#获取文件夹/文件的大小以及占用空间 转摘自:http://www.cnblogs.com/chenpeng-dota/articles/2176470.html
C#获取文件夹/文件的大小以及占用空间 今天,头给了个任务:写个方法,我会给你个路径,计算这个路径所占用的磁盘空间 . 然后,找了很多资料.但大部分都是获取文件夹/文件的大小的.对于占用空间的没有成品 ...
- Python 的PIL,可以解决ImportError The _imagingft C module is not installed
删除PIL相关文件 mv PIL /tmp pip install Pillow 安装Pillow后, 可能还会发生KeyError的错误, 检查项目源码后发现是 Image 模块的save函数中 ...
- mysql -- mysql基于ssl的主从复制
mysql基于ssl的主从复制由于mysql在复制过程中是明文的,所以就大大降低了安全性,因此需要借助于ssl加密来增加其复制的安全性. 主服务器node1:172.16.200.1从服务器node2 ...
- GitHub区域和工作流程
workspace:工作区 index:暂存区 repository:本地版本库 remote:远程仓库 首先到托管服务器上创建一个空版本库,例如在github.coding.oschina等 然后克 ...
- Elasticsearch index
POST/{index}/{type} Elasticsearch自动生成ID,自动生成的 ID 是 URL-safe. 基于 Base64 编码且长度为20个字符的 GUID 字符串. 这些 GUI ...
- 关于python全局变量
今天踩了一个坑,记录一下,避免后犯 在constant.py 中定义了一个全局变量 ZH_LIST= [],以个用于存储配置一些信息: 在views.py 中引用了这个ZH_LIST : 然后在app ...
- oracle 的使用
一. docker 模式下进入数据库 ubuntu@jiang:~$ sudo docker ps -a sudo: unable to resolve host jiang CONTAINER ID ...