java高并发系列 - 第4天:JMM相关的一些概念
JMM(java内存模型),由于并发程序要比串行程序复杂很多,其中一个重要原因是并发程序中数据访问一致性和安全性将会受到严重挑战。如何保证一个线程可以看到正确的数据呢?这个问题看起来很白痴。对于串行程序来说,根本就是小菜一碟,如果你读取一个变量,这个变量的值是1,那么你读取到的一定是1,就是这么简单的问题在并行程序中居然变得复杂起来。事实上,如果不加控制地任由线程胡乱并行,即使原本是1的数值,你也可能读到2。因此我们需要在深入了解并行机制的前提下,再定义一种规则,保证多个线程间可以有小弟,正确地协同工作。而JMM也就是为此而生的。
JMM关键技术点都是围绕着多线程的原子性、可见性、有序性来建立的。我们需要先了解这些概念。
原子性
原子性是指操作是不可分的,要么全部一起执行,要么不执行。在java中,其表现在对于共享变量的某些操作,是不可分的,必须连续的完成。比如a++,对于共享变量a的操作,实际上会执行3个步骤:
1.读取变量a的值,假如a=1
2.a的值+1,为2
3.将2值赋值给变量a,此时a的值应该为2
这三个操作中任意一个操作,a的值如果被其他线程篡改了,那么都会出现我们不希望出现的结果。所以必须保证这3个操作是原子性的,在操作a++的过程中,其他线程不会改变a的值,如果在上面的过程中出现其他线程修改了a的值,在满足原子性的原则下,上面的操作应该失败。
java中实现原子操作的方法大致有2种:锁机制、无锁CAS机制,后面的章节中会有介绍。
可见性
可见性是指一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。有些同学会说修改同一个变量,那肯定是可以看到的,难道线程眼盲了?
为什么会出现这种问题呢?
看一下java线程内存模型:

- 我们定义的所有变量都储存在
主内存中 - 每个线程都有自己
独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝) - 线程对共享变量所有的操作都必须在自己的工作内存中进行,不能直接从主内存中读写(不能越级)
- 不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行。(同级不能相互访问)
线程需要修改一个共享变量X,需要先把X从主内存复制一份到线程的工作内存,在自己的工作内存中修改完毕之后,再从工作内存中回写到主内存。
如果线程对变量的操作没有刷写回主内存的话,仅仅改变了自己的工作内存的变量的副本,那么对于其他线程来说是不可见的。
而如果另一个变量没有读取主内存中的新的值,而是使用旧的值的话,同样的也可以列为不可见。
共享变量可见性的实现原理:
线程A对共享变量的修改要被线程B及时看到的话,需要进过以下步骤:
1.线程A在自己的工作内存中修改变量之后,需要将变量的值刷新到主内存中
2.线程B要把主内存中变量的值更新到工作内存中
关于线程可见性的控制,可以使用volatile、synchronized、锁来实现,后面章节会有详细介绍。
有序性
有序性指的是程序按照代码的先后顺序执行。
为了性能优化,编译器和处理器会进行指令冲排序,有时候会改变程序语句的先后顺序,比如程序。
int a = 1; //1
int b = 20; //2
int c = a + b; //3
编译器优化后可能变成
int b = 20; //1
int a = 1; //2
int c = a + b; //3
上面这个例子中,编译器调整了语句的顺序,但是不影响程序的最终结果。
在单例模式的实现上有一种双重检验锁定的方式,代码如下:
public class Singleton {
static Singleton instance;
static Singleton getInstance(){
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
我们先看instance = new Singleton();
未被编译器优化的操作:
- 指令1:分配一款内存M
- 指令2:在内存M上初始化Singleton对象
- 指令3:将M的地址赋值给instance变量
编译器优化后的操作指令:
- 指令1:分配一块内存S
- 指令2:将M的地址赋值给instance变量
- 指令3:在内存M上初始化Singleton对象
现在有2个线程,刚好执行的代码被编译器优化过,过程如下:

最终线程B获取的instance是没有初始化的,此时去使用instance可能会产生一些意想不到的错误。
现在比较好的做法就是采用静态内部内的方式实现:
public class SingletonDemo {
private SingletonDemo() {
}
private static class SingletonDemoHandler{
private static SingletonDemo instance = new SingletonDemo();
}
public static SingletonDemo getInstance() {
return SingletonDemoHandler.instance;
}
}
java高并发系列
- java高并发系列 - 第1天:必须知道的几个概念
- java高并发系列 - 第2天:并发级别
- java高并发系列 - 第3天:有关并行的两个重要定律
- java高并发系列 - 第4天:JMM相关的一些概念
- java高并发系列 - 第5天:深入理解进程和线程
- java高并发系列 - 第6天:线程的基本操作
- java高并发系列 - 第7天:volatile与Java内存模型
- java高并发系列 - 第8天:线程组
- java高并发系列 - 第9天:用户线程和守护线程
- java高并发系列 - 第10天:线程安全和synchronized关键字
- java高并发系列 - 第11天:线程中断的几种方式
- java高并发系列 - 第12天JUC:ReentrantLock重入锁
java高并发系列连载中,总计估计会有四五十篇文章,可以关注公众号:javacode2018,获取最新文章。

java高并发系列交流群

java高并发系列 - 第4天:JMM相关的一些概念的更多相关文章
- java高并发系列-第1天:必须知道的几个概念
java高并发系列-第1天:必须知道的几个概念 同步(Synchronous)和异步(Asynchronous) 同步和异步通常来形容一次方法调用,同步方法调用一旦开始,调用者必须等到方法调用返回后, ...
- java高并发系列 - 第6天:线程的基本操作
新建线程 新建线程很简单.只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可. Thread thread1 = new Thread1(); t1.start(); 那么 ...
- java高并发系列 - 第12天JUC:ReentrantLock重入锁
java高并发系列 - 第12天JUC:ReentrantLock重入锁 本篇文章开始将juc中常用的一些类,估计会有十来篇. synchronized的局限性 synchronized是java内置 ...
- java高并发系列 - 第14天:JUC中的LockSupport工具类,必备技能
这是java高并发系列第14篇文章. 本文主要内容: 讲解3种让线程等待和唤醒的方法,每种方法配合具体的示例 介绍LockSupport主要用法 对比3种方式,了解他们之间的区别 LockSuppor ...
- java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能
这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能 ...
- java高并发系列 - 第16天:JUC中等待多线程完成的工具类CountDownLatch,必备技能
这是java高并发系列第16篇文章. 本篇内容 介绍CountDownLatch及使用场景 提供几个示例介绍CountDownLatch的使用 手写一个并行处理任务的工具类 假如有这样一个需求,当我们 ...
- java高并发系列 - 第17天:JUC中的循环栅栏CyclicBarrier常见的6种使用场景及代码示例
这是java高并发系列第17篇. 本文主要内容: 介绍CyclicBarrier 6个示例介绍CyclicBarrier的使用 对比CyclicBarrier和CountDownLatch Cycli ...
- java高并发系列 - 第21天:java中的CAS操作,java并发的基石
这是java高并发系列第21篇文章. 本文主要内容 从网站计数器实现中一步步引出CAS操作 介绍java中的CAS及CAS可能存在的问题 悲观锁和乐观锁的一些介绍及数据库乐观锁的一个常见示例 使用ja ...
- java高并发系列 - 第22天:java中底层工具类Unsafe,高手必须要了解
这是java高并发系列第22篇文章,文章基于jdk1.8环境. 本文主要内容 基本介绍. 通过反射获取Unsafe实例 Unsafe中的CAS操作 Unsafe中原子操作相关方法介绍 Unsafe中线 ...
随机推荐
- 关于ajax请求不到后台页面提示400 bad request的问题
解决方法一: 在contrller控制器中对应方法的 @RequestMapping注解中添加 method="RequestMethod.POST"属性
- spanish-1.1
1.1. ¿Cómo te llamas?Ana : ¿Cómo te llamas?Paco: Buenos dias. Yo soy Paco. Y tú, ¿cómo te llemas?Ana ...
- Windows下使用virtualenv创建虚拟环境
操作系统 : windowns10_x64Python版本:3.6.8virtualenv版本:16.7.7virtualenvwrapper版本:1.2.5 方式一:直接使用virtualenv 1 ...
- CSS属性继承
在CSS中有一些属性是可以继承的,跟继承家产一样,哎,一代一代的往下传,而有些属性就比较惨了,不能继承,只能自己来设置. 我简略的总结了一下一些可以继承和不可以继承的属性 可继承 1.字体系列属性:f ...
- 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器
目录 一.概述 二. 数据结构 2.1 链表 2.2 二叉堆 三. 从setTimeout理解Timer模块源码 3.1 timers.js中的定义 3.2 Timeout类定义 3.3 active ...
- 动态SQL与变量绑定
有时候动态sql需要进行变量的赋值,这个时候就需要调用系统的存储过程sp_executesql了.使用中还是有些注意事项,代码如下: --字符型字段需声明为NVARCHAR类型 ),) --动态SQL ...
- 神探Python程序员,带你千里捉小三!(附详情代码)
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者: 王翔 清风Python PS:如有需要Python学习资料的小伙伴 ...
- docker中安装宝塔面板
我的电脑是win10,安装的virtualbox其上装的ubutun14,ubutun也安装了docker,今天我补充一个完整的操作流程.怎么在docker中安装宝塔面板?先打个岔,这些命令总是记不住 ...
- Access Grid Control Properties 访问网格控件属性
In this lesson, you will learn how to access the properties of a list form's Grid Control in WinForm ...
- Ajax:实现后台验证js实现get方式的异步请求,判断用户名是否重复
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html& ...