首先我非常推荐学习Java的新手,或者只是看了视频但是并没有深入学习Java的人看一看这本书,这本书真的非常适合高并发编程的入门,我之前读《java编程思想》的并发一章,可能是翻译不好的原因,感觉确实是看不懂,但是看了这本书,我才对并发有了清晰的认识,这里并不是说《java编程思想》这本书写的不好,只是对一个对并发没有清晰概念的人,有些书籍确实并不能起到很好的入门引导的作用,当然这些都是我个人的看法,我感觉如果你对并发编程感兴趣,并且之前对并发并不了解,那么这本书一定可以帮助到你。
第一章、一些有用的概念
一、基础概念
我在学习Java的过程中,总是会对Java中各种各样的概念(术语)感到头大,这本书上来先将这些概念很清晰的解释了一遍,也是我对这本书非常推崇的原因之一。
1、同步:同步方法调用一旦开始,调用者必须等到方法返回后才能进行下一次调用
2、异步:异步方法调用后,我们可以继续调用其他的方法,当异步方法有返回值时,会在方法调用结束后返回,整个过程并不会影响调用者继续调用其他的方法。
例子:
同步调用就像我们在实体店买东西,我们要自己亲自去实体店,买完东西后我们要亲自把东西带回来,而异步就像网购,我们在完成网上支付时买东西这个事件就已经结束了,我们可以去做其他的一些事情,而卖方会将东西给我们送过来。
3、并发和并行:假设有两件事情,并行就是同时做两件事情,而并发是做一会第一件事情,做一会第二件事情。
例子:
假如我们左右手都可以写字,并行就是左右手同时写字,并发就是左手写一会,右手写一会。我们可能会有疑惑,这样的并发有什么用呢,其实cup的调度非常快的,我们根本感受不到并发时cpu对线程的调度,对我们来说,两个线程就像是同时执行的一样。
4、临界区:共享资源,可以被多个线程使用,但是每一次只能有一个线程访问它。
5、阻塞和非阻塞:通常用来形容多线程间的相互影响,假如一个线程访问了临界区,那么其他需要这个临界区资源的线程必须等待,那么这种情况就是阻塞。非阻塞强调没有一个线程可以妨碍其他线程的执行,所有线程都会不断的向前执行。
6、死锁:戳我一下
7、饥饿:指某一个或多个线程因为种种原因无法获得所需的资源,导致一直无法执行。
8、活锁:活锁是指多个线程都主动将资源让给其他的线程,导致没有一个线程能获得资源执行的现象。
例子:这个非常像我们在走路时对面堵着一个人,我向左走想避开他,他向右走想避开我,这样我们就有堵在一起了。
二、并发级别
1、阻塞:如上所示
2、无饥饿:线程的调度是公平的,先来先调度,后来后调度,不存在饥饿的现象。
3、无障碍:所有线程无需等待可以一起进入临界区,一旦临界区的数据遭到破坏,所有线程会回滚,保证数据的安全。无障碍的多线程程序并不一定能顺畅执行,当临界区中存在严重的冲突时,所有线程可能都会不断回滚,没有一个线程最终执行成功。
4、无锁:无锁是无障碍的一种,但是无锁保证必然有一个线程能够执行成功。
5、无等待:所有的线程都必须在有限步内完成。
三、JMM(Java的内存模型)
1、原子性:指一个操作是不可中断的,也就是说,当一个操作开始,那么就不会受其他线程的干扰。
2、可见性:指当一个线程更改了某一个共享变量的值,那么其他所有的线程都能立即知道这个改动。
例子:
假设A线程读取了共享变量i
的值,并先保存在了寄存器中,同时B线程修改了这个i
的值,这时,B线程的改动对A线程是不可见的,A线程仍然读取的是i
的旧值。
3、有序性:因为我们在编译程序时,编译器会对我们的程序进行优化,会改变对应机器指令的执行顺序,这时如果是多线程状态就会受到影响。
注意:指令重排可以保证串行语义的一致,但是并不保证多个线程间的语义也一致。
4、指令重排的规则:
- 程序顺序原则:一个线程内保证语义的串行性
- volatile规则:volatile变量的写先发生于读
- 锁规则:unlock必然发生在随后的lock前
- 传递性:A先于B,B先于C,那么A必然先于C
- 线程的start()方法先于它的每一个动作
- 线程的所有操作先于线程的终结
- 线程的interrupt()先于被中断线程的代码
- 对象的构造函数执行、结束先于finalize()方法