本文摘自《实战Java高并发程序设计》,了解详细内容推荐购买原版书籍。
第一章 走入并行世界
1.1何去何从的并行计算
使用并行程序目的:1、获得更好的性能。2、业务模型的需要,确实需要多个执行实体。
1.2基本的概念
1.2.1 同步(Synchronous)、异步(Asynchronous)
同步(Synchronous):同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
异步(Asynchronous):异步方法一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另一个线程中“真实”的执行。整个过程,不会阻碍调用者工作。
1.2.2 并发(Concurrency)与并行(Parallelism)
并发(Concurrency):偏重于多个任务交替执行。
并行(Parallelism):真正意义上“同时执行”。(出现在拥有多个CPU系统)
他们都可以表示两个或多个任务一起执行。
1.2.3 临界区
临界区:表示一种公共资源或共享数据,可被多个线程使用。每次只能有一个线程使用,一旦资源被占用,其他线程想用必须等待。
1.2.4 阻塞(Blocking)与非阻塞(Non-Blocking)
阻塞和非阻塞通常用来形容多线程间的相互影响。
阻塞(Blocking):一个线程占用了临界区资源,其他需要这个资源的线程需要在这个临界区等待。等待导致线程被挂起,为阻塞。
非阻塞(Non-Blocking):相反,强调没有一个线程可以妨碍其他线程执行。所有线程都会尝试不断向前执行。
1.2.5 死锁(Deadlock)、饥饿(Starvation)、活锁(Livelock)
死锁、饥饿和活锁都属于多线程的活跃性问题。发生这几种情况,可能相关线程不再活跃。
死锁(Deadlock):彼此占用其他线程资源。
饥饿(Starvation):指某一个或多个线程因为种种原因无法获得所需资源,导致一直无法执行。(可能线程优先级太低,或者某一线程一直占着关键资源不放)
相对于死锁,饥饿可能在未来一段时间解决(高优先级线程完成任务)
活锁(Livelock):线程智力不够,秉承“谦让”资源原则,主动释放资源给他人用,不断在两个线程中跳动,没有一个线程同时可以拿到所有资源执行。
1.3并发级别
由于临界区存在,多线程之间的并发必须受到控制。并发级别大致有:阻塞、无饥饿、无障碍、无锁、无等待。
1.3.1 阻塞(Blocking)
阻塞(Blocking):一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。当使用synchronized关键字,或重入锁时,得到的就是阻塞的线程。
无论是synchronized关键字,或重入锁,都会试图在执行后续代码之前,得到临界区的锁,得不到线程就会被挂起等待,知道占有所需资源。
1.3.2 无饥饿(Starvation-Free)
无饥饿(Starvation-Free):如果锁是公平的,饥饿就不会产生。
1.3.3 无障碍(Obstruction-Free)
无障碍(Obstruction-Free):无障碍是一种最弱的非阻塞调度。线程如果无障碍执行,都可以进入临界区。数据改坏进行回滚。(可以依赖“一致性标记”实现,线程操作之前读取保存标记,操作之后再读取,检查时候被修改过)
1.3.4 无锁(Lock-Free)
无锁(Lock-Free):无锁的并行都是无障碍的。无锁情况下,所有线程都能尝试对临界区进行访问,但是无锁的并发保证必然有一个线程能在有限步数内完成操作离开临界区。
1.3.5 无等待(Wait-Free)
无等待(Wait-Free):无锁要求一个线程有限步数完成,无等待要求所有,这样不会引起饥饿问题。
典型无等待机构RCU(Read-Copy-Update)。读数据不控制,所有线程无等待,写数据时,先去数据副本,修改副本,再合适时机回写数据。
1.4并行的Amdahl定律、Gustafson定律
1.4.1 Amdahl定律
**Amdahl定律:**使用多核CPU对系统进行优化,优化效果取决于CPU数量以及系统中串行化程序比重。
1.5回到Java:JMM(Java内存模型)
JMM定义一种规则,保证多个线程间可以有效地、正确的协同工作。
1.5.1 原子性(Atomicity)
原子性(Atomicity):指一个操作不可中断的。其实多个线程一起执行,一个操作一旦开始,就不会被其他线程干扰。线程A赋值给i为1,线程B赋值i为2,原子性就是无论怎么操作i只会是1或2。(long有64位,在32位系统中如果两个线程同时写入,对结果会有干扰)
1.5.2 可见性(Visibility)有序性(Ordering)
可见性(Visibility):指一个线程修改了某个共享变量的值,其他线程是否能立即知道这个修改。
有序性(Ordering):原因是程序在执行时,可能会进行指令重排,重排后的指令与原指令顺序未必一致。
指令执行步骤:
- 取指IF
- 译码和去寄存器操作数ID
- 执行或者有效地址计算EX
- 存储器访问MEM
- 写回WB
X表示中断