近日重新翻了一下《Java Concurrency in Practice》故以此文记之。
我觉得Java的并发可以从下面三个点去理解:
* 编译重排序
* 内存模型
* 资源竞争
这三个概念在Java并发中是非常重要的,最好也可以阅读相关资料理解一下。
一些概念
Amdahl定律
在包含N个处理器的机器中,最高的加速比为:Sppedup<=1/(F+(1-F)/n)。当N接近无穷大,最大的加速比趋近于1/F。因此,如果程序中有50%的计算需要串行执行,那么最高的加速比只有2;如果程序中有10%的计算需要串行执行,那么最高的加速比接近10。
Happens-Before
JMM为程序中所有的操作定义了一个便序关系,称之为Happens-Before。想要保证执行操作B的线程可以看到操作A的结果(无论A,B是 否在同一个线程中执行),那么在A和B之间必须满足Happens-Before关系。如果两个操作之间缺乏Happens-Before关系,那么 JVM可以对它们任意地重排序,这会导致不一致的运行结果。
Happens-Before的规则包括:
程序顺序规则 如果程序中A在操作B之前,那么线程中A操作将在B操作之前执行。(同一线程中)
监视器锁规则 在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。
volatile变量规则 对volatile变量的写入操作必须在对该变凉的读操作之前执行。
线程启动规则 在线程上对Thread.Start的调用必须在该线程中执行任何操作之前执行。
线程结束规则 线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,活着从Thread.join中成功返回,或者在调用Thread.isAlive时返回false。
中断规则 当一个线程在另一个线程上调用interrupt时,必须在被中断线程检测到interrupt调用之前执行(通过抛出InterruptedException,或者调用isInterrupted和interrupted)。
终结器规则 对象的构造函数必须在启动该对象的终结器之前执行完成。
传递性 如果操作A在操作B之前执行,并且操作B在操作C之前执行,那么操作A必须在操作C之前执行。
要注意的是Happens-Before规则更多的是从可见性的方面去理解,这样可以更容易的理解Happens-Before规则,以及JVM的重排序。例如程序顺序规则从可见性方面可以这样理解,在B操作执行的时候,A操作的执行结果对B操作可见;监视器锁规则可以这样理解,在同一个监视器上的解锁操作执行的时候,监视器上的加锁操作的执行结果对于解锁操作可见。
final域的安全发布
初始化安全性将确保对于被正确构造的对象,所有线程都能看到由构造函数为对象给各个final域设置的正确值,而不管采用何种方式来发布对象。 而且对于可以通过被正确构造对象中某个final域到达的任意变量(例如某个final数组的元素,活着由一个final域引用的HashMap的内容) 将同样对于其他线程是可见的。
对于含有final域的对象,初始化安全性可以防止对象的初始引用被重排序到构造过程之前。当构造函数完成时,构造函数对final域的所有写入操 作,以及对通过这些域可以到达的任何变量的写入操作,都将被“冻结”,并且任何活的该对象引用的线程都至少能确保看到被冻结的值。对于通过final域可 到达的初始变量的写入操作,将不会与构造过程后的操作一起被重排序。
初始化安全性只能保证通过final域可达的值从构造过程完成时开始的可见性。对于通过非final域可达的值,或者在构造过程完成后有可能改变的值,必须采用同步来确保可见性。
基本组件
闭锁
用于线程同步。线程可以堵塞在闭锁的await()方法上,直到countDown()操作将闭锁的计数减少到0。
1 /**
2 * 闭锁
3 */
4 public long countDownLatch() throws InterruptedException {
5 int nThreads = 5;
6 final CountDownLatch startGate = new CountDownLatch(1);
7 final CountDownLatch endGate = new CountDownLatch(nThreads);
8
9 for (int i = 0; i < nThreads; i++) {
10 Thread t = new Thread() {
11 public void run() {
12 try {
13 startGate.await();
14 sleep(1000);
15 } catch (InterruptedException e) {
16 e.printStackTrace();
17 }
18 endGate.countDown();
19 }
20 };
21 t.start();
22 }
23
24 long start = System.nanoTime();
25 startGate.countDown();
26 endGate.await();
27 long end = System.nanoTime();
28 return end - start;
29 }
FutureTask
可以从新起的工作线程中获取结果,会堵塞在get()方法,直到结果返回或者线程异常终止。下面只是一个简单的实例,实际使用注意处理抛出的异常。
1 /**
2 * FutureTask
3 */
4 public String futureTask() throws ExecutionException, InterruptedException {
5 FutureTask future = new FutureTask<>(() -> {
6 Thread.currentThread().sleep(3000);
7 return "Hello Future.";
8 });
9
10 Thread thread = new Thread(future);
11 thread.start();
12 System.out.println("future.get()");
13 return future.get();
14 }
31/3123>