前言收获
并行和并发的区别
- 并发:面对多个任务同时存在的情况下,能够处理它,不用管如何执行(可以交替执行、可以并行执行)
- 并行:面对多个任务同时存在的情况下,能够处理它,任务和任务间互不影响,一起执行
并行概念是并发概念的一个子集,如下图所示
并行架构:
- 位级并行
- 指令级并行
- 数据集并行
- 任务级并行
全书要讲解的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写会和普通读/写发生重排序。
什么是不正确的同步
- 一个线程对一个变量执行写入
- 另一个线程对同一个变量执行读取
- 写入和读取没有用同步排序
当发生这种情况时,都可以认为是发生了“数据竞争”
同步做了什么
- 排斥:一个线程获取了管程,另一个线程只能等到第一个线程释放后才能获取
- 可见性:在我们释放管程时,同步块会将本地内存刷新到主内存;获取管程之后,同步块会使本地缓存失效,重新去主内存中加载。
- happens-before:当一个操作happens-before另一个操作,那么就能向程序员保证JMM会让第一个先于第二个发生并对第二个可见