Java Concurrency
并发模型和分布式系统的相似性
并发系统中有多个线程在协同工作,而分布式系统是多个进程在协同工作,这些进程可能分布在不同的计算机上。线程和进程本身有许多相似之处。因为他们的相似性,所以它们经常相互借鉴思想。
- 平行式
- 流水线式 ( Actor 模型,channels)
- 函数式(函数可看作相互发送消息的 "Actor")
Java 开启新进程
- 继承 Thread 类,重载它的 run 方法,创建子类实例
- 实现 runnable 接口(接口的 run 方法),用 Thread(Runnalbe runnable) 创建 Thread 实例
然后调用实例的 start 方法
Race Conditions and Critical Sections
竞争情形和关键区。有些代码段如果同时被多个线程执行时,可能会因为线程执行顺序的不同而结果不同,称为关键区,关键区出现这种结果不同的情况称为关键区有竞争情形。这避免出现这种情况,可以用 synchronized block of Java code, locks or atomic variables 等线程同步的方法。为了增大并发量,应尽量把关键区缩小。
Thread Safety and Shared Resources
不会发生竞争情形的代码是线程安全的。竞争情形只发生在有共享资源的情况下。所以要搞清楚什么情况下有竞争情形就要知道 Java 中有哪些共享资源。
- Local Variables
Local variables are stored in each thread's own stack. That means that local variables are never shared between threads. That also means that all local primitive variables are thread safe. - Local Object References
所有的对象都是创建在 heap 上的,heap是线程共享的。如果方法创建的对象不逃逸出这个方法(即其他地方引用不到这个对象),那么这个方法也是线程安全的。 - Object Members
Object members are stored on the heap along with the object. Therefore, if two threads call a method on the same object instance and this method updates object members, the method is not thread safe. - 所以一种线程安全策略就是不让线程访问其他线程的对象。
Thread Safety and Immutability
竞争情形只发生在多线程访问相同的资源,而且一个或多个进程写资源的情况。所以另一种线程安全策略就是让共享的资源只能读不能写。
Java Memory Model
讲了 Java 的内存模型,以及硬件的存储结构(多级缓存)。这就导致了两个主要的问题。
-
不可见性
A CPU 读取并在自己的缓存中更新了变量 V,而没有立即更新内存中的 V,这里 B CPU 读取 V 仍是旧的 V。可用关键字 volatile 来解决。其修饰的变量,CPU 直接从内存中获取,如有更新立即 flush 到内存中。 -
竞争情形
不可见性很容易导致竞争情形。可用 synchronized 关键字来解决。其保证了同时只有一个线程能执行其关键区代码,并且关键区中访问变量都是直接访问内存,退出关键区时立即将修改 flush 到内存中。
线程信号
怎么实现线程之间通信呢?通过共享对象。一个对象的 wait notiy 方法
饥饿和公平
通常有三种情况引起线程饥饿
-
Threads with high priority swallow all CPU time from threads with lower priority.
-
Threads are blocked indefinately waiting to enter a synchronized block, because other threads are constantly allowed access before it.
-
Threads waiting on an object (called wait() on it) remain waiting indefinitely because other threads are constantly awakened instead of it.
然后作者在里引出了公平的概念。他所实现的 lock 类,内部有一个队列来保证先后顺序。改进了 synchronized 不公平的问题。看到这里明白了为什么 concurrent 包里要设计 locks 了吧。
Reentrance Lockout
选择有可重入性的锁,如果锁没有可重入性就要避免在锁里面再要锁