天天看点

并发编程之知识梳理 第一部分全文内容引用

Java 并发编程 第一部分

  • 全文内容
    • 并发
      • 是什么?
        • 同步与异步
        • 并发与并行
        • 进程、线程、协程
          • 协程
        • Java中的并发关键字
          • synchronized
            • 表现形式
            • Java对象头
          • volatile
            • CPU的术语定义
            • 使用优化
          • 原子操作
            • 处理器如何实现原子操作
            • Java如何实现
      • 为什么?
      • 挑战
        • 上下文切换
        • 死锁
        • 资源限制
    • 多线程
      • JMM
        • 主内存与工作内存
        • 内存间交互操作
        • 重排序
          • 从源代码到指令序列的重排序
          • as-if-serial语义
          • JMM解决方案
            • 内存屏障
        • happens-before
          • 为什么?
            • JMM的设计示意图
          • 是什么?
            • volatile规则
            • start()规则
            • join()规则
        • volatile的内存语义
          • 是什么?
          • 如何做到?
            • volatile重排序规则表
            • 基于保守策略的JMM内存屏障插入策略
            • 指令序列示意图
        • 锁的内存语义
          • 锁的释放-获取建立的happens-before关系
          • 锁内存语义的实现
        • final域的内存语义
        • 双重检查锁定与延迟初始化
          • 双重检查锁定的由来
          • 问题的根源
          • 基于volatile的解决方案
          • 基于类初始化的解决方案
          • 解决方案的比较
      • Java中的线程
        • 线程的状态
          • 有哪些?
          • 变迁
        • Daemon线程
        • 优先级
        • 启动和终止线程
          • 创建
          • 中断
        • 线程间的通信
          • synchronized
            • 底层实现
            • JVM的优化
          • 等待/通知机制
          • Thread.join()的使用
          • ThreadLocal的使用
        • Lock接口
          • 我有synchronized没有的
          • API
        • 队列同步器(AQS)
          • AbstractQueuedSynchronizer
          • 实现分析
            • 同步队列(FIFO)
            • 独占式同步状态获取与释放
            • 共享式同步状态获取与释放
            • 独占式超时获取同步状态
        • 重入锁(ReentrantLock)
        • 读写锁(ReentrantReadWriteLock)
        • LockSupport工具
        • Condition接口
          • API
          • 实现分析
  • 引用

你好! 这是我对于Java 并发编程相关知识点的梳理与思考,希望对你能有所帮助;菜鸟萌新,问题多多,欢迎指出,谢谢!本文章以图为主,如果文章中没有图片,麻烦移步: 链接.

全文内容

并发编程之知识梳理 第一部分全文内容引用

并发

并发编程之知识梳理 第一部分全文内容引用

是什么?

同步与异步

并发与并行

进程、线程、协程

  • 进程作为拥有资源的基本单位,线程作为调度和分配的基本单位。
  • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
  • 多进程的程序要比多线程的程序健壮(进程崩溃不影响其它进程),但在进程切换时,耗费资源较大,效率要差一些
  • 进程/线程之间的亲缘性决定了同一个进程/线程只在某个cpu上运行,避免因切换带来的CPU的L1/L2 cache失效而造成性能损失
    并发编程之知识梳理 第一部分全文内容引用
    并发编程之知识梳理 第一部分全文内容引用
协程
  • 不被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)
  • 极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;

Java中的并发关键字

synchronized
  • 由于Java中的线程是与操作系统的原生线程一一对应的,所以当阻塞一个线程时,需要从用户态切换到内核态执行阻塞操作,这是很耗时的操作,而synchronized的使用就会导致上下文切换

表现形式

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。

Java对象头

并发编程之知识梳理 第一部分全文内容引用
volatile
并发编程之知识梳理 第一部分全文内容引用

CPU的术语定义

并发编程之知识梳理 第一部分全文内容引用
  • 为了提高处理速度,处理器不直接和内存进行通信
  • Lock前缀指令会引起处理器缓存回写到内存(锁缓存/锁总线)
  • 一个处理器的缓存回写到内存会导致其他处理器的缓存无效(缓存一致性协议)

使用优化

  • 追加字节
原子操作
并发编程之知识梳理 第一部分全文内容引用

处理器如何实现原子操作

并发编程之知识梳理 第一部分全文内容引用

Java如何实现

并发编程之知识梳理 第一部分全文内容引用

为什么?

并发编程之知识梳理 第一部分全文内容引用
  • 对应用系统性能和吞吐量要求

挑战

并发编程之知识梳理 第一部分全文内容引用

上下文切换

  • 在单CPU时代多线程编程是没有太大意义的,并且线程间频繁的上下文切换还会带来额外开销

死锁

  • 互斥条件
  • 请求并持有条件
  • 不可剥夺条件
  • 环路等待条件

资源限制

多线程

JMM

主内存与工作内存

  • CPU、内存和 I/O 设备 存在速度差异性问题
    并发编程之知识梳理 第一部分全文内容引用
    并发编程之知识梳理 第一部分全文内容引用
    并发编程之知识梳理 第一部分全文内容引用
    并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用

内存间交互操作

并发编程之知识梳理 第一部分全文内容引用

重排序

从源代码到指令序列的重排序
并发编程之知识梳理 第一部分全文内容引用
as-if-serial语义
并发编程之知识梳理 第一部分全文内容引用
JMM解决方案
  • 对于处理器重排序,插入内存屏障指令

内存屏障

并发编程之知识梳理 第一部分全文内容引用

happens-before

如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens- before关系

为什么?

JMM的设计示意图

并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用
是什么?
并发编程之知识梳理 第一部分全文内容引用

volatile规则

并发编程之知识梳理 第一部分全文内容引用

start()规则

并发编程之知识梳理 第一部分全文内容引用

join()规则

并发编程之知识梳理 第一部分全文内容引用

volatile的内存语义

是什么?
并发编程之知识梳理 第一部分全文内容引用
如何做到?

volatile重排序规则表

并发编程之知识梳理 第一部分全文内容引用

基于保守策略的JMM内存屏障插入策略

并发编程之知识梳理 第一部分全文内容引用

指令序列示意图

volatile写

并发编程之知识梳理 第一部分全文内容引用

volatile读

并发编程之知识梳理 第一部分全文内容引用
  • 在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障
  • JSR-133为什么要增强volatile的内存语义?

    严格限制编译器和处理器对volatile变量与普通变量的重排序,确保volatile的写-读和锁的释放-获取具有相同的内存语义。

锁的内存语义

锁的释放-获取建立的happens-before关系
并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用
锁内存语义的实现
并发编程之知识梳理 第一部分全文内容引用

concurrent包的实现

并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用

final域的内存语义

并发编程之知识梳理 第一部分全文内容引用

双重检查锁定与延迟初始化

双重检查锁定的由来
问题的根源
并发编程之知识梳理 第一部分全文内容引用
基于volatile的解决方案
并发编程之知识梳理 第一部分全文内容引用
基于类初始化的解决方案
并发编程之知识梳理 第一部分全文内容引用
解决方案的比较
并发编程之知识梳理 第一部分全文内容引用

Java中的线程

线程的状态

有哪些?
并发编程之知识梳理 第一部分全文内容引用
变迁
并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用

Daemon线程

优先级

  • 程序正确性不能依赖线程的优先级高低

启动和终止线程

创建
  • 实现Runnable接口的run方法
  • 继承Thread类并重写run的方法
  • 使用FutureTask方式,实现Callable接口的call方法
中断
  • boolean isInterrupted():检测当前线程是否被中断,如果是返回true,否则返回false
  • boolean interrupted():检测当前线程是否被中断,如果是返回true,否则返回false。与isInterrupted不同的是,该方法如果发现当前线程被中断,则会清除中断标志,并且该方法是static方法,可以通过Thread类直接调用。另外从下面的代码可以知道,在interrupted()内部是获取当前调用线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。
  • 从Java的API中可以看到,许多声明抛出InterruptedException的方法(例如Thread. sleep(long millis)方法)这些方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。

线程间的通信

synchronized

底层实现

并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用

在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)

ObjectMonitor中有几个关键属性:

  • _owner:指向持有ObjectMonitor对象的线程
  • _WaitSet:存放处于wait状态的线程队列
  • _EntryList:存放处于等待锁block状态的线程队列
  • _recursions:锁的重入次数
  • _count:用来记录该线程获取锁的次数

    ObjectMonitor中有几个关键方法:

  • enter(TRAPS)
  • exit(TRAPS)
  • wait(jlong millis, bool interruptable, TRAPS)
  • nofity(TRAPS)

JVM的优化

  • 锁升级
    并发编程之知识梳理 第一部分全文内容引用
  • 锁消除

    JIT编译器借助逃逸分析(Escape Analysis)技术来判断同步块所使用的锁对象是否只能够被一个线程访问,如果被证实,就会取消对这部分代码的同步。

  • 锁粗化

    尽量减小锁的粒度,可以避免不必要的阻塞。但是如果在一段代码中连续的用同一个监视器锁反复的加锁解锁,甚至加锁操作出现在循环体中的时候,就会导致不必要的性能损耗,这种情况就需要锁粗化。

等待/通知机制
并发编程之知识梳理 第一部分全文内容引用
  • 使用wait()、notify()和notifyAll()时需要先对调用对象加锁
  • 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列
  • notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回
  • notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED
  • 从wait()方法返回的前提是获得了调用对象的锁
    并发编程之知识梳理 第一部分全文内容引用
Thread.join()的使用
  • 线程A执行了thread.join()语句
  • 当前线程A等待thread线程终止之后才从thread.join()返回
ThreadLocal的使用

并发编程之知识梳理 第一部分全文内容引用

Lock接口

我有synchronized没有的
特性 API
能响应中断 lockInterruptbly()
非阻塞式的获取锁 tryLock()
支持超时 tryLock(long time, timeUnit)
可实现公平锁 ReentrantLock(ture)
可以绑定多个条件 newCondition()
API
并发编程之知识梳理 第一部分全文内容引用

队列同步器(AQS)

基于模板方法模式

AbstractQueuedSynchronizer

访问或修改同步状态

并发编程之知识梳理 第一部分全文内容引用

可重写的方法

并发编程之知识梳理 第一部分全文内容引用

模板方法

并发编程之知识梳理 第一部分全文内容引用
实现分析

同步队列(FIFO)

并发编程之知识梳理 第一部分全文内容引用

节点的属性类型与名称以及描述

并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用

只有前驱节点是头节点才能够尝试获取同步状态的原因:

  • 头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态之后,将会唤醒其后继节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点。
  • 维护同步队列的FIFO原则

独占式同步状态获取与释放

节点自旋获取同步状态

并发编程之知识梳理 第一部分全文内容引用

解释:由于非首节点线程前驱节点出队或者被中断而从等待状态返回,随后检查自己的前驱是否是头节点,如果是则尝试获取同步状态。

独占式同步状态获取流程,也就是acquire(int arg)方法调用流程:

并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用

同步状态释放

并发编程之知识梳理 第一部分全文内容引用

共享式同步状态获取与释放

独占式超时获取同步状态

并发编程之知识梳理 第一部分全文内容引用

重入锁(ReentrantLock)

并发编程之知识梳理 第一部分全文内容引用

读写锁(ReentrantReadWriteLock)

并发编程之知识梳理 第一部分全文内容引用
并发编程之知识梳理 第一部分全文内容引用

ReentrantReadWriteLock展示内部工作状态的方法

并发编程之知识梳理 第一部分全文内容引用

LockSupport工具

并发编程之知识梳理 第一部分全文内容引用

LockSupport提供的阻塞和唤醒方法:

并发编程之知识梳理 第一部分全文内容引用

Condition接口

并发编程之知识梳理 第一部分全文内容引用
API
并发编程之知识梳理 第一部分全文内容引用
实现分析

同步队列与等待队列

并发编程之知识梳理 第一部分全文内容引用

当前线程加入等待队列:

并发编程之知识梳理 第一部分全文内容引用

节点从等待队列移动到同步队列

并发编程之知识梳理 第一部分全文内容引用

引用

[1]: 链接 https://mp.weixin.qq.com/s/-PRq4ChaCkEFB_DJyyKhvg

[2]: 《Java并发编程的艺术》 方腾飞 魏鹏 程晓明

[3]: 《Java并发编程之美》翟陆续,薛宾田