level 6
lbj2004032
楼主
锁的劣势
从前面的章节可以看到,使用一致的加锁协议来协调对共享状态的访问,确保当线程持有守护变量的锁时,线程都能独占地访问这些变量,并且保证随后获得同一锁的线程都能看见该线程对变量所作的修改。
Java虚拟机能够对非竞争锁的获取和释放进行优化,让它们非常高效,但是如果有多个线程同时请求锁,Java虚拟机就需要向操作系统寻求帮助。倘若了出现这种情况,一些线程将可能被挂起,并稍后恢复运行。从线程开始恢复,到它真正被调度前,可能必须等待其他线程完成它们的调度限额规定的时问。挂起和恢复线程会带来很大的开销,并通常伴有冗长的中断。对于基于锁,并且其操作过度细分的类(比如同步容器类,大多数方法只包含很少的操作),当频繁地发生锁的竞争时,调度与真正用于工作的开销间的比值会很可观。
加锁还有其他的缺点。当一个线程正在等待锁时,它不能做任何其他事情。如果一个线程在持有锁的情况下发生了延迟(原因包括页错误、调度延迟,或者类似情况),那么其他所有需要该锁的线程都不能前进了。如果阻塞的线程是优先级很高的线程,持有锁的线程优先级较低,那么会造成性能风险,被称为优先级倒置(priority inversion)。即虽然更高的优先级占先,但它仍然需要等待锁被释放,这导致它的优先级会降至与优先级较低的线程相同的水平。如果持有锁的线程发生了永久性的阻塞(因为无限循环、死锁、活锁和其他活跃度失败),所有等待该锁的线程都不会前进了。
即使忽略上述的风险,加锁对于小的操作而言,仍然是重量级(heavy weight)的机制,比如自增操作。需要有更好的技术用来管理线程之问的竞争。在Java 5.0中,使用原子变量类(atomic variable classes)能够高效地构建非阻塞算法。
8.2. 原子变量类
在JDK 5.0之前,如果不使用本机代码,就不能用Java语言编写无等待
无锁定的算法。在java.util.concurrent中添加原子变量类之后,这种情况发生了变化。本节了解这些新类开发高度可伸缩的无阻塞算法。
java.util.concurrent.atomic 包中添加原子变量类。所有原子变量类都公开“比较并设置”原语(与比较并交换类似),这些原语都是使用平台上可用的最快本机结构(比较并交换、加载链接/条件存储,最坏的情况下是旋转锁)来实现的。
原子变量类共有12个,分成4组:计量器、域更新器(field updater)、数组以及复合变量。最常用的原子变量是计量器:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。他们都支持CAS(比较并设置,详细参考第3章);AtomicInteger和AtomicLong还支持算术运算。
原子变量类可以认为是volatile变量的泛化,它扩展了volatile变量的概念,来支持原子条件的比较并设置更新。读取和写入原子变量与读取和写入对volatile变量的访问具有相同的存取语义。
虽然原子变量类表面看起来与SynchronizedCounter例子(参考3.2.1节)一样,但相似仅是表面的。在表面之下,原子变量的操作会变为平台提供的用于并发访问的硬件原语,比如比较并交换。
调整具有竞争的并发应用程序的可伸缩性的通用技术是降低使用的锁对象的粒度,希望更多的锁请求从竞争变为不竞争。从锁转换为原子变量可以获得相同的结果,通过切换为更细粒度的协调机制,竞争的操作就更少,从而提高了吞吐量。
下面的程序是使用原子变量后的计数器:
package jdkapidemo;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger value = new AtomicInteger();
public int getValue() {
return value.get();
}
public int increment() {
return value.incrementAndGet();
}
public int increment(int i) {
return value.addAndGet(i);
}
public int decrement() {
2010年07月02日 14点07分
1
从前面的章节可以看到,使用一致的加锁协议来协调对共享状态的访问,确保当线程持有守护变量的锁时,线程都能独占地访问这些变量,并且保证随后获得同一锁的线程都能看见该线程对变量所作的修改。
Java虚拟机能够对非竞争锁的获取和释放进行优化,让它们非常高效,但是如果有多个线程同时请求锁,Java虚拟机就需要向操作系统寻求帮助。倘若了出现这种情况,一些线程将可能被挂起,并稍后恢复运行。从线程开始恢复,到它真正被调度前,可能必须等待其他线程完成它们的调度限额规定的时问。挂起和恢复线程会带来很大的开销,并通常伴有冗长的中断。对于基于锁,并且其操作过度细分的类(比如同步容器类,大多数方法只包含很少的操作),当频繁地发生锁的竞争时,调度与真正用于工作的开销间的比值会很可观。
加锁还有其他的缺点。当一个线程正在等待锁时,它不能做任何其他事情。如果一个线程在持有锁的情况下发生了延迟(原因包括页错误、调度延迟,或者类似情况),那么其他所有需要该锁的线程都不能前进了。如果阻塞的线程是优先级很高的线程,持有锁的线程优先级较低,那么会造成性能风险,被称为优先级倒置(priority inversion)。即虽然更高的优先级占先,但它仍然需要等待锁被释放,这导致它的优先级会降至与优先级较低的线程相同的水平。如果持有锁的线程发生了永久性的阻塞(因为无限循环、死锁、活锁和其他活跃度失败),所有等待该锁的线程都不会前进了。
即使忽略上述的风险,加锁对于小的操作而言,仍然是重量级(heavy weight)的机制,比如自增操作。需要有更好的技术用来管理线程之问的竞争。在Java 5.0中,使用原子变量类(atomic variable classes)能够高效地构建非阻塞算法。
8.2. 原子变量类
在JDK 5.0之前,如果不使用本机代码,就不能用Java语言编写无等待
无锁定的算法。在java.util.concurrent中添加原子变量类之后,这种情况发生了变化。本节了解这些新类开发高度可伸缩的无阻塞算法。
java.util.concurrent.atomic 包中添加原子变量类。所有原子变量类都公开“比较并设置”原语(与比较并交换类似),这些原语都是使用平台上可用的最快本机结构(比较并交换、加载链接/条件存储,最坏的情况下是旋转锁)来实现的。
原子变量类共有12个,分成4组:计量器、域更新器(field updater)、数组以及复合变量。最常用的原子变量是计量器:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。他们都支持CAS(比较并设置,详细参考第3章);AtomicInteger和AtomicLong还支持算术运算。
原子变量类可以认为是volatile变量的泛化,它扩展了volatile变量的概念,来支持原子条件的比较并设置更新。读取和写入原子变量与读取和写入对volatile变量的访问具有相同的存取语义。
虽然原子变量类表面看起来与SynchronizedCounter例子(参考3.2.1节)一样,但相似仅是表面的。在表面之下,原子变量的操作会变为平台提供的用于并发访问的硬件原语,比如比较并交换。
调整具有竞争的并发应用程序的可伸缩性的通用技术是降低使用的锁对象的粒度,希望更多的锁请求从竞争变为不竞争。从锁转换为原子变量可以获得相同的结果,通过切换为更细粒度的协调机制,竞争的操作就更少,从而提高了吞吐量。
下面的程序是使用原子变量后的计数器:
package jdkapidemo;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger value = new AtomicInteger();
public int getValue() {
return value.get();
}
public int increment() {
return value.incrementAndGet();
}
public int increment(int i) {
return value.addAndGet(i);
}
public int decrement() {