目录
Java的内存模型JMM(Java memory model)
多线程必须了解的几个概念
同步和异步
(1)同步synchronous:同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
(2)异步asynchronous:异步方法调用更像是一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。
并行和并发
- 并发concurrency:表示两个或者多个任务一起执行,侧重多个任务交替执行,多个任务之间有可能还是串行的。
- 并行parrallelism:表示两个或者多个任务一起执行,是真正意义上的“同时执行”。
临界区
临界区是表示一种公共资源或者说是共享资源,可以被多个线程使用,但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。
阻塞和非阻塞
阻塞和非阻塞用来形容多线程之间的相互影响。
(1)阻塞blocking:如果说是一个线程占用了临界区的资源,那么其他所有需要这个资源的线程就必须在这个临界区进行等待,等待就会致使线程挂起,这个情况就是阻塞。
(2)非阻塞non-blocking:没有一个线程可以妨碍其他线程的执行。所有的线程都会不断向前执行。
死锁、饥饿和活锁
(1)死锁deadlock:都不愿意释放自己的,导致这种状态一直维持下去。
(2)饥饿starvation:一个或者多个线程因为种种原因无法获取所需要的资源,导致一直无法执行。比如,情况一:线程优先级太低,高优先级的线程不断的抢占它的资源,导致低优先级的线程无法工作。情况二:某个线程一直占着关键的资源不放,导致其他需要这个资源的线程无法正常执行。饥饿相比较死锁而言,有可能在未来一段时间内解决的。
(3)活锁livelock:线程主动将资源释放给其他线程使用,那么就会出现资源不断的在两个线程之间跳动,而没有一个线程可以同时拿到所有的资源而正常执行。
并发级别
(1)阻塞blocking:一个线程是阻塞的,那么在其他线程释放资源之前,当前线程无法继续执行。当我们使用synchronized关键字或者是重入锁时,我们得到的就是阻塞的线程。
(2)无饥饿starvation-free:
公平锁(满足先来后到,不管新来的线程的优先级多高,要是想获取到资源,就必须乖乖排队,那么所有的线程都有机会执行)。
非公平锁(系统允许高优先级的线程插队,这样就会导致低优先级的线程产生饥饿)
(3)无障碍obstruction-free:无障碍是一种最弱的非阻塞调度。
阻塞其实是一种悲观的策略,认为两个线程之间很有可能发生不幸的冲突,所有会阻塞,以保证共享数据。
非阻塞的调度其实是一种乐观的策略。认为多个线程之间很有可能不会发生冲突,或者说是这种概率很小,大家都可以无障碍的执行,但是一旦检测到冲突,就应该进行回滚。
无阻塞的多线程程序可能会存在一定的问题,那就是可能所有的线程都会不断的回滚自己的操作,而没有一个线程可以走出临界区,解决这种问题的一种方式就是用“一致性标记”来实现。线程在操作之前,先读取这个标记,在操作完成之后,再次读取,检查这个标记是否被更改过,如果两者是一致的,则说明资源访问没有冲突。如果不一致,说明资源可能在操作过程中与其他线程冲突,需要重试操作。
(4)无锁lock-free:
无锁的并行都是无障碍的。
在无锁的情况下,所有的线程都会尝试对临界区进行访问,但是不同的是,无锁的并发保证必然有一个线程能够在有限步内完成操作离开临界区。
(5)无等待wait-free
无锁只要求一个线程可以在有限步内完成操作,而无等待则在无锁的基础上进一步进行了扩展,它要求所有的线程都必须在有限步内完成,不会引起饥饿问题。
无等待结构较为常见的一个应用是RCU(read-copy-update):对数据的读可以不加控制,所以说所有的读线程都是无等待,它们既不会被锁定等待也不会引起任何冲突,但是在写数据的时候,先取得原始数据的副本,接着只修改副本数据,修改完之后,在合适的时机回写数据。
并行两个重要定律
(1)Amdahl定律
加速度比=优化前的系统耗时/优化后的系统耗时
Tn = 1/(F+(1-F)/n)(F是程序中只能串行执行的比例,Tn表示使用n个处理器优化后的耗时,)
根据amdahl定律,CPU的数量越多,串行化的比重越低,则优化效果越好。仅仅提高系统的CPU数量而不降低程序的串行化比重,也是无法提高系统的性能的。
(2)Gustafson定律
加速比 = n - F(n-1)
根据Gustafson定律,如果串行化比例很小,并行化比例很大,那么加速比就是处理器的个数。
Java的内存模型JMM(Java memory model)
JMM的关键主要是围绕着多线程的原子性、可见性和有序性来展开的。
原子性
原子性atomicity:是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
例如:我们定义一个全局的静态变量int i,两个线程同事对他赋值,线程1赋值1,线程2赋值-1,那么我们不管这两个线程以何种方式、何种步调工作,对于i的值要么是1,要么是-1,线程1和线程2之间是没有干扰的。这就是原子性的一个特点,不可中断的。
可见性
可见性visibility:是指当一个线程修改了某一个共享变量的值,其他线程释放能立即知道这个修改。
有序性
有序性ordering:
指令重排可以保证串行语义一致,但是确实不能保证多线程间的语义也是一致的。
哪些指令是不能重排的(happen-before原则)
(1)程序顺序原则:一个线程内保证语义的串行化。
(2)volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性。
(3)锁规则:解锁unlock必然发生在随后的加锁lock前。
(4)传递性:A先于B,B先于C,那么A必然先于C。
(5)线程的start方法先于他的每一个动作。
(6)线程的所有操作先于线程的终结thread.jion()。
(7)线程的中断interrupt先于被中断线程的代码。
(8)对象的构造函数执行、结束先于finalize方法。