您的位置:首页 > 编程语言 > Java开发

java并发编程实践学习(15)原子变量与非阻塞同步机制

2017-02-22 19:55 651 查看
近来很多关于并发算法的研究机构都聚焦在非阻塞算法上,这种算法使用低层原子化的机器指令取代锁,比如比较并交换

一、锁的劣势

当频繁发生锁的竞争时,调度与真正用于工作的开销时间的比会变得很可观。volatile变量与锁相比是更清凉的同步机制,因为他们不会引起上下文的切换和线程调度。

加锁还有其他缺点。当一个线程正在等待锁时,它不能做任何其他事情。如果一个线程在持有锁的情况下发生了延迟,如果阻塞的线程是优先级很高的线程,持有锁的线程是优先级较低的线程,那么会造成优先级倒置的风险,即使高优先级占先,他仍然需要等待锁被释放,这导致他的优先级会降至与优先级较低的线程相同的水平。

二、硬件对并发的支持

针对多处理器系统设计的处理器提供了特殊的指令,用来管理并发访问的共享数据。早期处理器具有原子化的测试并设置,获取并增加,以及交换指令,现代的处理器都具有一些形式的原子化的读-改-写指令,比如比较并交换和加载链接/存储条件。操作系统和JVM使用这些指令来实现锁和并发的数据结构。

1、比较并交换

包括IA32和、Sparc在内的大多数处理器使用的架构方案都实现了比较并交换指令(CAS)。CAS有三个操作数-内存位置V、旧的预期值A和新值B、当且仅当V符合旧预期值A时,CAS用新值B原子化的更新V的值;否则它什么都不做。

当多个线程试图调用CAS同时更新想的的变量时,其中一个会胜出,并更新变量的值,而其它都会失败,失败的线程不会被挂起;

模拟CAS操作

@ThreadSafe
public class SimulatedCAS{
@GuardedBy("this")
private int value;

public synchronized int get(){
return value;
}

public synchronized int compareAndSwap(int expectedValue,int new Value){
int = oldValue = value;
if(oldValue == value){
value = new Value;
}
return oldValue;
}

public synchronized boolean compareAndSet(int expectedValue, int newValue){
return (expectedValue == compareAndSwap(expectedValue,newValue));
}
}


2、JVM对CAS的支持

在Java5.0中引入了CAS的底层支持,将int、long和对象的引用暴露给CAS操作,并且JVM把它们编译为底层硬件提供的最有效的方法。在支持CVS的平台上,运行时把它们编排成恰当的机器指令,在CAS不可用的情况下,JVM才会使用自旋锁,这些底层的JVM支持用于那些具有原子化变量的类(java.util.concurrent.atomic中的AtomicXXX),而且这些原子变量类还用于直接或间接的实现java.util.concuttent中的大部分实现。

三、原子变量类

原子变量类,提供了广义的volatile变量,以及支持原子的、条件的读-写改操作

原子变量类共有12个,分成四组:计量器、域更新器、数组以及复合变量。最常用的原子变量是计量器:AtomicInteger、AtomicLong、AtomicBoolean以及AtomicReference。它们都支持CAS,AtomicInteger和AtomicLong还支持算数运算,对于short和byte把它们的值强制转化为int;对于浮点数,使用floatToIntBits或doubleToLongBits。

原子化数组类(只有Integer、Long和Reference版本可用)它的元素可以被原子化的更新。原子数组类为数组元素提供了volatile的访问语义,这是普通数组元素没有的特性。

4.非阻塞算法

一个线程的失败或挂起不影响其他线程的失败或挂起,这样的算法被称为为阻塞算法;如果算法的每一步中都有一些线程能够继续执行,这样的算法被称为锁自由算法。在线程间使用CAS进行协调,这样的算法如果能构建正确的话,它既是非阻塞的,又是锁自由的。

1、非阻塞栈

在链式容器类中,有时你可以 不必在进行转化了而把它变为对单独链接的修改,你可以使用一个AtomicReference来表达每一个必须被原子的链接。

栈是最简单的链式数据结构:每个元素仅仅引用唯一的元素,并且每个元素值被一个元素引用。

使用Treiber算法的非阻塞栈

public class ConcurrentStack<E> {
AtomicReference<Node<E>> head = new AtomicReference<Node<E>>();
public void push(E item) {
Node<E> newHead = new Node<E>(item);
Node<E> oldHead;
do {
oldHead = head.get();
newHead.next = oldHead;
} while (!head.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = head.get();
if (oldHead == null)
return null;
newHead = oldHead.next;
} while (!head.compareAndSet(oldHead,newHead));
return oldHead.item;
}
static class Node<E> {
final E item;
Node<E> next;
public Node(E item) { this.item = item; }
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息