浅谈 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异常 以前虽然知道一 ...
随机推荐
- 55 道MySQL基础题
1.一张表,里面有 ID 自增主键,当 insert 了 17 条记录之后, 删除了第 15, 16, 17 条记录,再把 Mysql 重启,再 insert 一条记 录,这条记录的 ID 是 18 ...
- java 多线程 Thread 锁ReentrantLock;Condition等待与通知;公平锁
1,介绍: import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; 在JA ...
- CentOS7.6 鲜为人知的/etc/resolv.conf 之 /etc/resolv.conf.save (保持/etc/resolv.conf不被修改:/etc/dhcp/dhclient-enter-hooks 无效之/etc/resolv.conf被清空的特殊案例)
目的: 用户可以自定义/etc/resolv.conf内容,且不被系统修改. 常规方法1: /etc/sysconfig/network-scripts/ifcfg-eth0 网卡配置文件中增加PEE ...
- React-Router(一)
React-Router基础知识 import React from "react"; import { BrowserRouter as Router, Switch, Rout ...
- Linux(centos)创建用户并分配权限
创建名为 elas的用户 adduser elas 初始化elas的密码 passwd elas 显示 新的 密码: 重新输入新的 密码: passwd:所有的身份验证令牌已经成功更新. 进行授权 个 ...
- C++常用工具库(C语言文件读写,日志库,格式化字符串, 获取可执行文件所在绝对路径等)
前言 自己常用的工具库, C++ 和C语言实现 使用cmake维护的项目 持续更新..... 提供使用范例, 详见example文件夹 windows使用的VS通过了的编译. Linux(Ubuntu ...
- 【LeetCode】12. Integer to Roman 整数转罗马数字
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:roman, 罗马数字,题解,leetcode, 力扣, ...
- 【LeetCode】415. Add Strings 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 [LeetCode] 题目地址:https:/ ...
- 【LeetCode】809. Expressive Words 解题报告(Python)
[LeetCode]809. Expressive Words 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http ...
- B. Arpa's weak amphitheater and Mehrdad's valuable Hoses
B. Arpa's weak amphitheater and Mehrdad's valuable Hoses time limit per test 1 second memory limit p ...