【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 ...
随机推荐
- python web自动化测试框架搭建(功能&接口)——环境搭建
自动化测试框架一般需要实现以下通用功能 执行前准备 结束后清理 执行步骤输出 执行结果输出 错误.失败截图 测试报告 发送邮件 日志 需要的软件和python第三方库有: 通用: JDK Eclips ...
- Linux操作系统(二)_快速入门
环境 安装VM ware,输入VM key 在VM上安装CentOS 6.5 设置网络,能在本机上ping通 通过终端连接工具:Xshell或SecureCRT,连接Linux服务器 实操可能出现的问 ...
- Git 内部原理
首先要弄明白一点,从根本上来讲 Git 是一个内容寻址(content-addressable)文件系统,并在此之上提供了一个版本控制系统的用户界面. 马上你就会学到这意味着什么. git objec ...
- LINUX查看服务器硬件配置(转)
LINUX查看硬件配置 1. 查看所有硬件的型号 dmidecode | more # dmidecode 2.2 SMBIOS 2.5 present. 170 structures oc ...
- 最新版的node安装和配置注意事项
node在安装的时候,如果你不想用默认的安装路径,可以自定义路径进行安装,例如我的安装路径如下:F:\Program Files\nodejs 安装完成后,要对node进行配置: 在F:\Progra ...
- Facade——外观模式
Facade外观模式,也是比较常用的一种模式,基本上所有软件系统中都会用到. GOF 在<设计模式>一书中给出如下定义:为子系统中的一组接口提供一个一致的界面, Facade 模式定义了一 ...
- maven3常用命令、java项目搭建、web项目搭建详细图解(转)
转自:http://blog.csdn.net/edward0830ly/article/details/8748986 maven3常用命令.java项目搭建.web项目搭建详细图解 2013-0 ...
- Solr添加索引
发送请求: http://localhost:8080/solr/update/?stream.body= <delete><id>id值</id></del ...
- error: Error trying to parse settings: Unexpected trailing characters in Packages\User\Preferences.sublime-settings:9:2 reloading settings Packages/User/Preferences.sublime-settings
error: Error trying to parse settings: Unexpected trailing characters in Packages\User\Preferences.s ...
- git 初始化提交项目
Git初始化本地已有项目,并推送到远端Git仓库操作1. 创建本地项目,在项目根目录执行git init命令git init 2. 在git服务器上创建一个仓库,这里使用GitHub创建一个仓库.例如 ...