为什么使用多线程

  1. 更多的处理器核心数(硬件的发展使 CPU 趋向于更多的核心数,如果不能充分利用,就无法显著提升程序的效率)
  2. 更快的响应时间(复杂的业务场景下,会存在许多数据一致性不强的操作,如果将这些操作并行执行,则响应时间会大大缩短)
  3. 更好的编程模型(Java 为多线程编程提供了良好的编程模型及众多工具,使用起来非常方便)

并发编程的挑战

  1. 线程的创建和销毁需要开销(可以通过线程池解决)
  2. 更多的线程意味着更多的上下文切换,有些情况下速度可能不如单线程
    1. 无锁并发编程,多线程竞争锁时会引起上下文切换
    2. 乐观锁:使用 CAS 算法更新数据,而不需要锁
    3. 使用最少线程:当任务很少却创建大量的线程来处理的时候,很多线程是处于等待状态的。这部分线程也会引起上下文切换
    4. 协程:在单线程中实现多任务的调度
  3. 死锁
    1. 避免一个线程同时获取多个锁
    2. 避免一个线程在锁内同时占用多个资源
    3. 尝试使用其他锁,如 lock.tryLock(timeout)来替代使用内部锁机制
    4. 对于数据库锁,加锁和解锁必须在同一个事物内
  4. 软硬件的资源限制(例如网络带宽 2mb/s,某个资源下载速度是 1mb/s,启动 10个线程下载不会让下载速度到 10mb/s)
  5. 指令重排序可能会打破程序原本的语义
    1. 编译器和处理器一般情况下仅对满足数据依赖条件的两个操作不做重排序

      1. 数据依赖条件:两个操作访问同一个变量 + 其中一个操作是写操作

Java 内存模型(JMM)

并发编程首先要解决的两个问题是:线程之间的通讯与同步,Java 多线程采用的是共享内存模型来进行线程通讯,在共享内存模型里,同步是显式执行的

Java 内存模型的抽象结构示意图

由上图可以看出,如果两个线程需要通讯的话,需要以下步骤

  1. 线程A把本地内存A里面的共享变量副本刷新到主内存
  2. 线程B读取主内存中已经更新过的共享变量

Volatile 的内存语义

volatile 的特性

  • 可见性:对一个 volatile 变量的读,总是能看到任意线程对该变量最后一次的写入

  • (伪)原子性:对任意单个 volatile 变量的读/写具有原子性,但是类似于 v++ 这样的操作不具备原子性

volatile 读、写的内存语义

  • 线程 A 写一个 volatile 变量,实质上是对接下来要读这个变量的线程发了(其对共享变量所做修改)消息
  • 线程 B 读一个 volatile 变量,实质上是接收之前某个线程发送的(在写这个变量之前对共享变量所做的修改的)消息
  • 线程 A 写,随后线程 B 读,这个过程实质上是线程 A 通过主内存向线程 B 发送消息

Happens-before

JSR-133 对 happens-before 的规则描述

  1. 程序顺序规则:一个线程中,按照程序顺序,前面的操作 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
  1. 监视器锁规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁
// 假设 x 的初始值是 10,线程 A 执行完代码块后 x 的值会变成 12(解锁),接着线程 B 进入代码块(加锁),此时触发了监视器锁规则。因此线程 A 对共享变量的操作能够被 B 所看见,也就是线程 B 能够看到 x == 12
synchronized (this) { // 加锁
// x 是共享变量,初始值 = 10
if (this.x < 12) {
this.x = 12;
}
} // 解锁
  1. 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
}
}
}
  1. 传递性:如果 A happens-before B,且 B happens-before C,那么 A happens-before C
// 有了这条规则,我们可以得出: 1 happens-before 4,那么第 4 步看到的 x 值将是 42。
// 也就是通过 volatile 变量,线程 A 向线程 B 发送了它对变量 x 所做的修改的消息
  1. start() 规则:指线程 A 启动子线程 B 后,子线程 B 能够看到线程 A 在启动子线程 B 前的操作

  2. join() 规则:如果线程A执行操作 ThreadB.join() 并成功返回,那么线程B中的任意操作 happens-before 于线程 A 从 ThreadB.join() 操作成功返回

JSR-133 对 happens-before 的定义

  1. 如果一个操作 happens-before 于另一个操作,那么第一个操作的执行结果对第二个操作可见,并且第一个操作先于第二个操作执行
  2. 两个操作存在 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的更多相关文章

  1. 转载:浅谈Java多线程的同步问题【很好我就留下来,多分共享】

    转载:http://www.cnblogs.com/phinecos/archive/2010/03/13/1684877.html#undefined 多线程的同步依靠的是对象锁机制,synchro ...

  2. 浅谈Java多线程的同步问题 【转】

    多线程的同步依靠的是对象锁机制,synchronized关键字的背后就是利用了封锁来实现对共享资源的互斥访问. 下面以一个简单的实例来进行对比分析.实例要完成的工作非常简单,就是创建10个线程,每个线 ...

  3. 浅谈Java多线程同步机制之同步块(方法)——synchronized

    在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...

  4. 浅谈Java多线程

    线程与进程 什么是进程? 当一个程序进入内存中运行起来它就变为一个进程.因此,进程就是一个处于运行状态的程序.同时进程具有独立功能,进程是操作系统进行资源分配和调度的独立单位. 什么是线程? 线程是进 ...

  5. 浅谈Java多线程中的join方法

    先上代码 新建一个Thread,代码如下: package com.thread.test; public class MyThread extends Thread { private String ...

  6. 浅谈Java的集合框架

    浅谈Java的集合框架 一.    初识集合 重所周知,Java有四大集合框架群,Set.List.Queue和Map.四种集合的关注点不同,Set 关注事物的唯一性,List 关注事物的索引列表,Q ...

  7. 浅谈java类集框架和数据结构(2)

    继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...

  8. 浅谈Java线程安全

    浅谈Java线程安全 - - 2019-04-25    17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...

  9. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

随机推荐

  1. java 图形化小工具Abstract Window Toolit

      老掉牙的历史 Java1.0在发布的时候,就为我们提供了GUI操作的库,这个库系统在所有的平台下都可以运行,这套基本的类库被称作抽象窗口工具集(Abstract Window Toolit),简称 ...

  2. java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader

    1.等待通知机制: 等待通知机制的原理和厨师与服务员的关系很相似: 1,厨师做完一道菜的时间不确定,所以厨师将菜品放到"菜品传递台"上的时间不确定 2,服务员什么时候可以取到菜,必 ...

  3. echarts map 地图在react项目中的使用

    需求 展示海南省地图,点击市高亮展示,并在右侧展示对应市的相关数据. 准备工作 Echarts 海南地图json 效果图 代码 index.tsx import React, { useRef, us ...

  4. htmlunit设置只采集html,取消对css,javascript支持

    引入htmlunit依赖 <!-- https://mvnrepository.com/artifact/net.sourceforge.htmlunit/htmlunit --> < ...

  5. soui(1)之一个半透明的窗口

    一个样式 xml源码 <SOUI name="mainWindow" title="@string/title" bigIcon="ICON_L ...

  6. 【LeetCode】441. Arranging Coins 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 模拟计算 二分查找 数学公式 日期 题目地址:htt ...

  7. 1266 - Points in Rectangle

    1266 - Points in Rectangle    PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 3 ...

  8. 【MySQL作业】多表连接查询——美和易思多表连接查询应用习题

    点击打开所使用到的数据库>>> 1.获取订单 ID 为 4 的订购明细信息,要求输出商品名.单价和件数. 连接三张表:订单表.订单明细表和商品表. select goodsName ...

  9. 部署Kubernetes Cluster

    中文学习网站:https://www.kubernetes.org.cn/doc-16 部署docker服务 所有节点部署docker服务 curl -sSL https://get.daocloud ...

  10. Kafka版本介绍Version2.4.0

    1.说明 Kafka的版本从0.11.0.X到1.0.X, 再到2.0.X大版本, 其实没有经过几个版本, 只是版本号变化较大. 2.最新发布版本 截止本文章2020年2月22号发布时, Kafka ...