《七周七并发模型》第一章第一天

前言收获

并行和并发的区别

  • 并发:面对多个任务同时存在的情况下,能够处理它,不用管如何执行(可以交替执行、可以并行执行)
  • 并行:面对多个任务同时存在的情况下,能够处理它,任务和任务间互不影响,一起执行

并行概念是并发概念的一个子集,如下图所示

并行架构:

  • 位级并行
  • 指令级并行
  • 数据集并行
  • 任务级并行

全书要讲解的7个模型:

  • 线程与锁:其他模型的技术基础,虽然存在不足但仍是众多软件的首选
  • 函数式编程:消除了可变状态,根本上是线程安全的,易于并行执行
  • Clojure之道——分离标识与状态:命令式编程和函数式编程混搭的方案,在两种编程方式中取得平衡来发挥两者的优势
  • actor: 适用于共享内存模型和分布式内存模型,也适合解决地理分布型问题,能够提供强大的容错
  • 通信顺序进程(Communicating Sequential Processes ,CSP): 该模型和actor很类似,两者都基于消息传递。不过CSP侧重于传递信息的通道,而actor模型侧重于通道两端的实体,使用CSP模型的代码会有明显不同的风格
  • 数据级并行:GPU,如果要进行有限元分析、流体力学计算或其他的大量数字计算,GPU的性能将是不二选择
  • Lambda: 综合了MapReduce和流式处理的特点,是一种可以处理多种大数据问题的架构

后续学习要带上以下几个问题:

  • 这个模型适用于解决并发问题、并行问题还是两者皆可?
  • 这个模型适合哪种并行架构
  • 这个模型是否有利于我们写出容错性强的代码,或用于解决分布式问题的代码?

互斥和内存模型

因为该部分讲的不深,而且经过两本并发书籍的洗礼,理解起来较为简单,就将总结的话记一下:

  • 对共享变量的所有访问都需要被同步化
  • 读线程和写线程都需要同步化
  • 按照约定的全局顺序来获取多吧锁
  • 当持有锁时不要访问外星方法(不了解的方法,里面可能会对部分资源加锁)
  • 持有锁的时间尽可能短

自学篇之JMM

自学篇之JSR-133的FAQ

原文出自(http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html)

究竟什么是内存模型?

在现代处理器系统里,通常会划分几个内存缓冲层。它们能够提高访问数据的速度并且降低共享内存总线的流量(因为缓存层就直接可以满足处理器需求),它们的结构示意图如下所示。那么两个处理器在同一时间查看同个内存地址时,什么情况下能看到一样的值。这就是内存可见性问题

处理器、缓存、共享内存总线间的关系

第一个影响可见性的原因是,强内存模型能直接保证 对同一个内存地址在任何时候都能看到相同的值;弱内存模型需要 内存屏障(memory barriers),这些指令会要求处理器刷新或无效化本地缓冲区,所以就能看到其他处理器的写入。因为强内存模型对内存指令的需求较少(注意,不是不需要,只是需求少),所以强内存模型易于编程。然而近代处理器都是鼓励往弱内存模型发展,由于弱内存模型对缓存一致性比较放松,跨多处理器和大量内存会有更好的扩展性。

第二个影响可见性的原因就是重排序,在内存模型给定的边界里任由编译器、内存、处理器优化。

所以Java内存模型主要描述了程序中的变量和物理机中内存、寄存器间的关系,JMM尽可能让编译器、硬件可以执行优化。

其他语言有内存模型吗

很多编程语言,像C、C++,就没有直接支持多线程。如果想避免所有重排序发生在编译器里,整个架构都需要依赖线程库、依赖编译器、系统

JSR-133讲了什么

JSR-133 为Java定义了一个新的内存模型,修复了早期的内存模型问题。为了实现它,final、volatile的语义均得到改变。通过完整的JSR-133语义可以了解到JSR-133的全貌

JSR-133的目的包含以下几点:

  • 保持已存在的安全保证,加强其他安全,比如变量值不能凭空出现,每个变量值的出现都应该有理有据
  • 正确同步的程序语义应该尽可能简单、直观
  • 对于不正确同步的程序语义,应尽可能的减小风险。
  • 对于每个程序员来说,都应该能正确推导多线程和内存间的交互关系
  • JVM应该被设计为:能为大部分主流的硬件架构提供高性能
  • 应该保证初始化安全。如果一个对象被正确的构造(没有发生this逸出),就算没有同步,所有引用该对象的线程都将看到该构造器设置的final变量的值。
  • 对现有代码造成最小的影响

重排序意味着什么

当访问某个变量时,可能出现它的值和预期指定的值不一样。编译器会重排序指令,处理器也可能会重排序指令。数据在寄存器、缓冲、内存间移动的顺序不按程序指定的来。
尽管如此,单线程程序是看不到重排序的影响的,即中途发生重排序,结果仍然不会改变。然而重排序会在多线程中起到作用:由于一个线程的修改可以被另外一个线程观察到,所以重排序中途修改的值会被另一个线程观测到。

旧的内存模型有什么问题

难理解,出现范围广。具体表现在以下两个例子里:

  • final变量被一个线程读时可能出现默认值,另一个线程读时可能出现构造值。这就意味着不可变对象发生变化了。
  • volatile写会和普通读/写发生重排序。

什么是不正确的同步

  1. 一个线程对一个变量执行写入
  2. 另一个线程对同一个变量执行读取
  3. 写入和读取没有用同步排序
    当发生这种情况时,都可以认为是发生了“数据竞争”

同步做了什么

  • 排斥:一个线程获取了管程,另一个线程只能等到第一个线程释放后才能获取
  • 可见性:在我们释放管程时,同步块会将本地内存刷新到主内存;获取管程之后,同步块会使本地缓存失效,重新去主内存中加载。
  • happens-before:当一个操作happens-before另一个操作,那么就能向程序员保证JMM会让第一个先于第二个发生并对第二个可见