浅谈 Java 多线程(一) --- JMM
为什么使用多线程
- 更多的处理器核心数(硬件的发展使 CPU 趋向于更多的核心数,如果不能充分利用,就无法显著提升程序的效率)
- 更快的响应时间(复杂的业务场景下,会存在许多数据一致性不强的操作,如果将这些操作并行执行,则响应时间会大大缩短)
- 更好的编程模型(Java 为多线程编程提供了良好的编程模型及众多工具,使用起来非常方便)
并发编程的挑战
- 线程的创建和销毁需要开销(可以通过线程池解决)
- 更多的线程意味着更多的上下文切换,有些情况下速度可能不如单线程
- 无锁并发编程,多线程竞争锁时会引起上下文切换
- 乐观锁:使用 CAS 算法更新数据,而不需要锁
- 使用最少线程:当任务很少却创建大量的线程来处理的时候,很多线程是处于等待状态的。这部分线程也会引起上下文切换
- 协程:在单线程中实现多任务的调度
- 死锁
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源
- 尝试使用其他锁,如 lock.tryLock(timeout)来替代使用内部锁机制
- 对于数据库锁,加锁和解锁必须在同一个事物内
- 软硬件的资源限制(例如网络带宽 2mb/s,某个资源下载速度是 1mb/s,启动 10个线程下载不会让下载速度到 10mb/s)
- 指令重排序可能会打破程序原本的语义
- 编译器和处理器一般情况下仅对满足数据依赖条件的两个操作不做重排序
- 数据依赖条件:两个操作访问同一个变量 + 其中一个操作是写操作
- 编译器和处理器一般情况下仅对满足数据依赖条件的两个操作不做重排序
Java 内存模型(JMM)
并发编程首先要解决的两个问题是:线程之间的通讯与同步,Java 多线程采用的是共享内存模型来进行线程通讯,在共享内存模型里,同步是显式执行的
Java 内存模型的抽象结构示意图

由上图可以看出,如果两个线程需要通讯的话,需要以下步骤
- 线程A把本地内存A里面的共享变量副本刷新到主内存
- 线程B读取主内存中已经更新过的共享变量
Volatile 的内存语义
volatile 的特性
可见性:对一个 volatile 变量的读,总是能看到任意线程对该变量最后一次的写入
(伪)原子性:对任意单个 volatile 变量的读/写具有原子性,但是类似于 v++ 这样的操作不具备原子性
volatile 读、写的内存语义
- 线程 A 写一个 volatile 变量,实质上是对接下来要读这个变量的线程发了(其对共享变量所做修改)消息
- 线程 B 读一个 volatile 变量,实质上是接收之前某个线程发送的(在写这个变量之前对共享变量所做的修改的)消息
- 线程 A 写,随后线程 B 读,这个过程实质上是线程 A 通过主内存向线程 B 发送消息
Happens-before
JSR-133 对 happens-before 的规则描述
- 程序顺序规则:一个线程中,按照程序顺序,前面的操作 happens-before 于后续的任意操作,例如
// A happens-before B, B happens-before C
double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C
- 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁
// 假设 x 的初始值是 10,线程 A 执行完代码块后 x 的值会变成 12(解锁),接着线程 B 进入代码块(加锁),此时触发了监视器锁规则。因此线程 A 对共享变量的操作能够被 B 所看见,也就是线程 B 能够看到 x == 12
synchronized (this) { // 加锁
// x 是共享变量,初始值 = 10
if (this.x < 12) {
this.x = 12;
}
} // 解锁
- volatile变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读
// 根据规则 1,可以确定 1 happens-before 2 、3 happens-before 4
// 根据规则 3,可以确定 2 happens-before 3
// 推导到这里,好像感觉没什么用,并不能确定第 4 步输出的 x 值,且继续往下看第 4 条规则
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42; // 1
v = true; // 2
}
public void reader() {
if (v == true) { // 3
sout(x); // 4
}
}
}
- 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C
// 有了这条规则,我们可以得出: 1 happens-before 4,那么第 4 步看到的 x 值将是 42。
// 也就是通过 volatile 变量,线程 A 向线程 B 发送了它对变量 x 所做的修改的消息
start() 规则:指线程 A 启动子线程 B 后,子线程 B 能够看到线程 A 在启动子线程 B 前的操作
join() 规则:如果线程A执行操作 ThreadB.join() 并成功返回,那么线程B中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回
JSR-133 对 happens-before 的定义
- 如果一个操作 happens-before 于另一个操作,那么第一个操作的执行结果对第二个操作可见,并且第一个操作先于第二个操作执行
- 两个操作存在 happens-before 关系,并不意味着 Java 一定按照 happens-before 关系指定的顺序执行,只要能保证程序语义不变的重排序也并不非法
定义 1 是 JMM 对程序员的承诺,从程序员的角度来说,如果 A happens-before B,则 A 的操作对 B 可见
定义 2 是对 Java 设计者的约束,对于设计者来说只要不改变程序语义,想怎么优化、想怎么重排序都可以。这么做的目的就是在不改变程序语义的前提下,尽可能高的提高程序的并发度
总结
本篇简要介绍了 Java 内存模型结构、volatile 和 happens-before 的相关概念,volatile 是 Java 中并发容器和原子类实现的基石,理解其内存语义有助于后续理解并发容器和原子类的实现原理;而 happens-before 是 JMM 最核心的概念。对于 Java 程序员来说,理解 happens-before 是理解 JMM 的关键,希望通过此篇可以帮助读者解决 Java 并发编程中遇到的内存可见性问题
浅谈 Java 多线程(一) --- JMM的更多相关文章
- 转载:浅谈Java多线程的同步问题【很好我就留下来,多分共享】
转载:http://www.cnblogs.com/phinecos/archive/2010/03/13/1684877.html#undefined 多线程的同步依靠的是对象锁机制,synchro ...
- 浅谈Java多线程的同步问题 【转】
多线程的同步依靠的是对象锁机制,synchronized关键字的背后就是利用了封锁来实现对共享资源的互斥访问. 下面以一个简单的实例来进行对比分析.实例要完成的工作非常简单,就是创建10个线程,每个线 ...
- 浅谈Java多线程同步机制之同步块(方法)——synchronized
在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...
- 浅谈Java多线程
线程与进程 什么是进程? 当一个程序进入内存中运行起来它就变为一个进程.因此,进程就是一个处于运行状态的程序.同时进程具有独立功能,进程是操作系统进行资源分配和调度的独立单位. 什么是线程? 线程是进 ...
- 浅谈Java多线程中的join方法
先上代码 新建一个Thread,代码如下: package com.thread.test; public class MyThread extends Thread { private String ...
- 浅谈Java的集合框架
浅谈Java的集合框架 一. 初识集合 重所周知,Java有四大集合框架群,Set.List.Queue和Map.四种集合的关注点不同,Set 关注事物的唯一性,List 关注事物的索引列表,Q ...
- 浅谈java类集框架和数据结构(2)
继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...
- 浅谈Java线程安全
浅谈Java线程安全 - - 2019-04-25 17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...
- 浅谈Java的throw与throws
转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...
随机推荐
- Python软件目录结构
目录组织方式 关于如何组织一个较好的Python工程目录结构,已经有一些得到了共识的目录结构.在Stackoverflow的这个问题上,能看到大家对Python目录结构的讨论. 这里面说的已经很好了, ...
- 云主机tracert外网无返回需在安全组入方向加ICMP Time Exceeded TTLexpired in transit
- 缓存Bigkey坚决不要用,拆分是王道
大家好,我是架构摆渡人.这是实践经验系列的第四篇文章,这个系列会给大家分享很多在实际工作中有用的经验,如果有收获,还请分享给更多的朋友. 背景介绍 在高并发的业务场景中,缓存是必须要上的,用来扛高并发 ...
- 【LeetCode】1470. 重新排列数组 Shuffle the Array (Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode-cn ...
- 教学日志:javaSE-java中的数据类型和运算符
一.java中的标识符 /* 标识符的命名规范: 硬性要求: 1.必须以字母._下划线.美元符$开头 2.其它部分可以是字母.下划线"_".美元符"$"和数字的 ...
- 在linux(deepin)系统下查看当前ip地址与用户名
在linux(deepin)系统下查看当前ip地址与用户名 查看当前ip地址 方式一: hostname -I 方式二: ifconfig -a 如下图所示: 其中192.168.11.66即为当前系 ...
- Type-C扩展芯片|Type-C扩展方案|CSCapstone|扩展坞方案选型
一.关于Capstone Capstone科技于2018年8月在台湾成立.团队成员的多样性将硅谷和台湾的才华横溢的人联系在一起,以进行协作和取得优越成就. Capstone科技是由一个经验丰富的研发团 ...
- 中文字体css编码转换
各大网站的字体选择 网站 字体 腾讯 font: 12px "宋体","Arial Narrow",HELVETICA; 淘宝 font: 12px/1.5 t ...
- 关于linux的一点好奇心(一):linux启动过程
一直很好奇,操作系统是如何工作的?我们知道平时编程,是如何让代码跑起来的,但那些都是比较高层次的东西.越往后,你会越觉得,像是空中楼阁,或者说只是有人帮你铺平了许多道理,而你却对此一无所知. 1. 操 ...
- [学习笔记] RabbitMQ的安装使用
安装 使用命令行安装,会自动管理依赖(推荐): choco install rabbitmq 安装包安装: 以管理员身份安装64位的 Erlang. 下载并安装 RabbitMQ 服务.下载地址. R ...