DougLea:《Concurrent Programming in Java:Design Principles and Patterns》
1、In Action
(1)执行的可能路径
Java代码会变成字节码指令。
对于指令系列中有N个指令和T个线程,没有循环或条件分支的简单情况,总的执行路径数量等于(NT)! / (N!)的T次幂。
根据java内存模型,32位值的赋值操作是不可中断的。如 int a = 2;
根据JVM规约,64位值的赋值需要两次32位赋值。
框架——每个方法调用都需要一个框架。该框架包括返回地址、传入方法的参数,以及方法中定义的本地变量。这是定义一个调用堆栈的标准技术。现代编程语言用来实现基本函数调用和递归调用。
本地变量——方法作用范围内定义的每个变量。所有非静态方法至少有一个变量this,代表当前对象,即接收导致方法调用的(当前线程内)大多数最新消息的对象。
运算对象栈——java虚拟机中的许多指令都有参数。运算对象栈是放置参数的地方。
操作对象都处理对于方法而言是本地的信息。故多个线程之间并无冲突。
理解线程之间如何相互干涉的,并不一定要精通字节码。有必要尽量理解内存模型,明白什么是安全的,什么是不安全的。
必须要知道:
a、什么地方有共享对象/值;
b、哪些代码会导致并发读/写问题;
c、如何防止这种并发问题发生。
(2)Executor框架
Executor框架支持利用线程池进行复杂的执行。Executor框架将线程放到池中,自动调整其大小,并在必要时重建线程。它支持future,一种通用的并发编程构造。Executor能与实现了Runnable的类协同工作,也能与实现了Callable接口的类协同工作。Callback看来就像是Runnable,但它能返回一个结果。
(3)
2、TIPS
(1)要保持并发系统整洁,应该将线程管理代码约束于少数几处控制良好的地方。
为每个职责创建单独的类。
(2)非锁定的解决方案
java5虚拟机利用现代处理器支持可靠、非锁定更新的设计优点。
如java5有一系列的新类,如AtomicBoolean、AtomicInteger、AtomicReference等。可以使用非锁定的手段。
现代处理器有比较交换(CAS)操作,这种操作类似于数据库中的乐观锁定,而其同步版本则类似于保守锁定。
关键字synchronized总是要求上锁,即使第二个线程并不更新同一值时也是如此。尽管这种固有锁的性能一直在提升,但代价仍然昂贵。
非上锁的版本假定多个线程通常并不频繁修改同一个值。
CAS的操作是原子的。逻辑上是这样:当某个方法试图更新一个共享变量,CAS操作就会验证要赋值的变量是否保有上一次的已知值。若是,就修改变量值。若不是,则不会碰变量,因为另一个线程正在试图更新变量值。要更新数据的方法(通过CAS操作)查看是否修改并持续尝试。
(3)非线程安全类
a、数据库连接
b、java.util中的容器
c、servlet
线程安全的集合:java.util.concurrent中的集合,如ConcurrentHashMap。
(4)出现错误时,有两种解决方法
a、基于客户代码的锁定
b、基于服务端的锁定(建议)——修改服务端代码解决问题,同时也修改了客户代码。若无法修改服务端代码,可以使用适配器模式修改API,添加锁定。
更好的方法是使用线程安全的集合和扩展接口。
(5)死锁
死锁的发生需要4个条件:
a、互斥:当多个线程需要使用同一资源,而此资源无法在同一时间为多个线程所用;
b、上锁及等待:当某个线程获取一个资源,在获取到其他全部所需资源并完成其工作之前,不会释放这个资源;
c、无抢先机制:线程无法从其他线程处夺取资源。一个线程持有资源时,其它线程获得这个资源的唯一手段就是等待该线程释放资源;
d、循环等待
这4个条件都是死锁必需的。只要其中一个不满足,死锁就不会发生。
避免死锁的一种策略是规避互斥条件。可以使用允许同时使用的资源,如AtomicInteger;
线程1同时需要资源1和资源2、线程2同时需要资源2和资源1,只要强制线程1和线程2以同样次序分配资源,循环等待就不会发生。
有许多避免死锁的方法,有些会导致饥饿,另外一些会导致对CPU能力的大量耗费和降低响应率。
将解决方案中与线程相关的部分分割出来,再加以调整和试验。
小心记录在何种条件下测试失败。
测试线程代码的工具支持:
IBM提供ConTest的工具,它能对类进行装置,令非线程安全代码更有可能失败。
(6)
3、PS
(1)系统在什么地方耗费时间
a、I/O——使用套接字、连接到数据库、等待虚拟内存交换等;
b、处理器——数值计算、正则表达式处理、垃圾回收等。
若代码运行速度主要和处理器相关,增加处理器硬件就能提升吞吐量。CPU运算周期是有上限的,只是增加线程的话并不会提升受处理器限制的代码的速度。
若吞吐量与IO有关,则并发编程能提升运行效率。当系统的某个部分在等待IO,另一部分就可以利用等待的时间处理其他事,从而有效利用了CPU能力。
IO操作不耗费处理器能力。
(2)