基本概念
1. 同步和异步
- 同步和异步通常用来形容一次方法调用
- 同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
- 异步方法调用更像一个消息传递,一旦开始,方法调用就会以及返回,调用者就可以继续后续的工作。如果异步调用需要返回结果,那么当这个异步调用真实完成时则会通知调用者。
2. 并行和并发
- 都表示两个或多个任务一起执行。
- 并发偏重多个任务交替执行,而多个任务之间有可能还是串行的。
- 并行是真正意义上的“同时执行”。
3. 临界区
- 用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次只有一个线程可以使用它,其他线程必须等待。
4. 阻塞和非阻塞
- 阻塞:一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待,导致线程挂起。
- 非阻塞:与之相反,强调没有一个线程可以妨碍其他线程执行,所有线程都会尝试不断向前执行。
5. 死锁、饥饿、活锁
- 都是线程活跃性问题。
- 死锁:四辆小车问题。各自占用了其他线程请求的资源,同时又在请求其他线程所占有的资源,而且都不肯释放,导致死锁。
- 饥饿:一个线程因为某种原因(优先级低)一直无法获得所需要的资源,导致无法运行。相较于死锁,活锁在未来一段时间内可以解决
- 活锁:线程互相谦让,主动将资源释放给他人使用,导致资源在两个线程之间不断跳动,没有一个线程可以同时拿到所有资源而正常运行,这就是活锁。
6. 并发级别
6.1 阻塞(blocking)
- 一个线程是阻塞的,那么在其他线程释放资源前,当前线程无法执行。使用sychronized和重入锁时,得到阻塞线程。
6.2 无饥饿(starving-free)
- 由于线程优先级,线程调度时总会倾向于满足高优先级的进程,所以是非公平的,就会产生饥饿。
- 采用公平的锁,线程都可以执行,就是无饥饿的。
6.3 无障碍(obstruction-free)
- 最弱的非阻塞调度,两个线程都是无障碍的执行,那么不会因为临界区的问题导致一方被挂起。
- 如果都进入了临界区,修改了共享数据,发生冲突,就会回滚。
- 相较于悲观策略的阻塞方式,无障碍方式是乐观的。
6.4 无锁(lock-free)
- 无锁的并行都是无障碍的。无锁下,所有线程都可以尝试对临界区进行访问,但是无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。
- 在临界区竞争失败的线程,会不断重试知道成功,如果一直运气不好,就会饥饿,线程停止不前。
6.5 无等待(wait-free)
- 无锁要求有一个线程可以在有限步内完成操作。
- 无等待在无锁的基础上更进一步,要求所有线程必须在有限步内完成操作。这样就没有饥饿。
- 例如RCU(Read-copy-update):对数据读不加控制,读线程都是无等待的,写数据时,先取得原数据的副本,接着修改副本数据,在合适的时机回写数据。
7. JMM
7.1 原子性(Atomicity)
- 原子性指一个操作是不可中断的。即使多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。例如:int型变量赋值是原子的,long型不是(32位,64位区别)。
7.2 可见性(Visibility)
- 可见性指一个线程修改了一个共享变量的值,其他线程可以立刻知道此修改。
- 缓存优化,硬件优化,指令重排序以及编辑器优化都有可能导致这一问题。
7.2 有序性(Ordering)
- 指令重排序后,写在前面的代码未必会先执行。
- 在串行程序中,指令重排不会影响语义逻辑。
- 并行程序中,线程A的指令执行顺序在线程B中是没有保证的。
- 指令重排可以提高CPU性能。
7.3 指令重排规则(Happen-before)
- 程序顺序原则:一个线程内保证语义的串行性
- volatile规则:volatile变量的写,先发生于读
- 锁规则:unlock先于后面的lock
- 传递性:A先于B,B先于C,则A先于C
- 线程的start()方法先于它的每一个动作
- 线程的所有操作先于它的终结(Thread.join())
- 线程的中断(interrupt())先于被中断的代码
- 对象的构造函数执行、结束先于finalize()方法
网友评论