锁的劣势:
-
如果线程持有锁而延迟,会导致其他的线程的等待。
-
高优先级的线程阻塞,而低优先级的线程持有锁造成 优先级反转(priority inversion)。
-
如果持有锁的线程被永久地阻塞,所有等待这个锁的线程就无法执行下去,造成 死锁(dead lock)。
硬件对并发的支持
-
比较并交换CAS:我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少
-
非阻塞的计数器:
@ ThreadSafe
public class CasCounter {
private SimulatedCAS value ;
public int getValue(){
return value .get();
}
public int increment(){
int v;
do {
v = value .get();
} while (v != value .compareAndSwap(v, v + 1));
return v + 1;
}
}
- CAS的主要缺点是:它将使调度者处理竞争问题(通过重试、回退、放弃),而在使用锁中能自动处理竞争问题(线程在获得锁之前将一直阻塞)。
原子变量类
-
原子变量比锁的粒度更细,量级更轻,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的
-
原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作
-
12个原子变量类,分为4组:标量类、更新器类、数组类、复合变量类
-
volatile类型的数组仅在数组引用上具有volatile语义,而在其元素上则没有
-
基本类型的包装类是不可修改的,而原子变量类是可修改的
-
使用ReentrantLock、AtomicInteger、ThreadLocal比较,通常情况下效率排序是ThreadLocal > AtomicInteger > ReentrantLock。
非阻塞算法
-
非阻塞算法的特性:某项工作具有不确定性,必须重新执行
-
非阻塞的栈
//使用 Treiber 算法的非阻塞堆栈
@ThreadSafe
public class ConcurrentStack <E> {
AtomicReference<Node<E>> top = new AtomicReference<Node<E>>();
public void push(E item) {
Node<E> newHead = new Node<E>(item);
Node<E> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = top.get();
if (oldHead == null)
return null;
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node <E> {
public final E item;
public Node<E> next;
public Node(E item) {
this.item = item;
}
}
}
- 非阻塞的链表
- 有两个指针指向尾部的节点:当前最后一个元素的next指针,以及尾节点
- Michael-Scott 非阻塞队列算法中的插入
@ThreadSafe
public class LinkedQueue<E> {
private static class Node <E> {
final E item;
final AtomicReference<LinkedQueue.Node<E>> next;
public Node(E item, LinkedQueue.Node<E> next) {
this.item = item;
this.next = new AtomicReference<LinkedQueue.Node<E>>(next);
}
}
private final LinkedQueue.Node<E> dummy = new LinkedQueue.Node<E>(null, null);
private final AtomicReference<LinkedQueue.Node<E>> head
= new AtomicReference<LinkedQueue.Node<E>>(dummy);
private final AtomicReference<LinkedQueue.Node<E>> tail
= new AtomicReference<LinkedQueue.Node<E>>(dummy);
public boolean put(E item) {
LinkedQueue.Node<E> newNode = new LinkedQueue.Node<E>(item, null);
while (true) {
LinkedQueue.Node<E> curTail = tail.get();
LinkedQueue.Node<E> tailNext = curTail.next.get();
if (curTail == tail.get()) {
if (tailNext != null) { // A
// 队列处于中间状态,推进尾节点
tail.compareAndSet(curTail, tailNext); // B
} else {
// 处于稳定状态,尝试插入新节点
if (curTail.next.compareAndSet(null, newNode)) { // C
// 插入操作成功,尝试推进尾节点
tail.compareAndSet(curTail, newNode); // D
return true;
}
}
}
}
}
}
-
原子的域更新器:
- 表示现有volatile域的一种基于反射的“视图”,从而能够在已有的volatile域上使用CAS。
- 域更新器没有与某个特定的实例关联在一起,可以更新目标类的任何实例中的域。
- 域更新器提供的原子性保证比普通原子类更弱一些,因为无法保证底层的域不被直接修改
-
ABA问题
-
ABA问题是一种异常现象:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令时就可能出现这种问题(主要在没有垃圾回收机制的环境中)。在CAS操作中将判断“V的值是否仍然是A?”,并且如果是的话就继续执行更新操作。在大多数情况下,这种判断是完全足够的。然而,有时候还需要知道“自从上次看到V的值为A以来,这个值是否发生了变化?” 在某些算法中,如果V的值首先由A变成B,再由B变成A,那么仍然被认为是发生了变化,并需要重新执行算法中的某些步骤。
-
有一个相对简单的解决方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号。即使这个值由A变成B,然后又变成A,版本号也将是不同的。
-