ConcurrentLinkedQueue原理(下)
来源:互联网 发布:怎样注册网络主播 编辑:程序博客网 时间:2024/06/15 05:52
ConcurrentHashMap,它是一个以Concurrent开头的并发集合类,其原理是通过增加锁和细化锁的粒度来提高并发度。
而ConcurrentLinkedQueue这个类采用了另一种提高并发度的方式:非阻塞算法(Non-blocking),第一次实现了无锁的并发。
谈到这里,先要介绍一下非阻塞算法。其实非阻塞算法并不是什么神秘高深的东西,它需要有一套硬件和指令的配合(似乎目前大多数pc都能支持),
主要解决的问题是:在许多时候,一个线程A持有其他线程B,C,D所需要的资源,但线程A遭遇网络阻塞,或数据库连接阻塞,或页面阻塞等。
这时B,C,D就必须等待 A执行结束才能继续向前推进。这种情况在队列、堆栈等数据结构中也会经常出现,典型的如将一个队列中的数据取出来需要锁整个队列,
也就是说,在对队列的操作中,各个线程实际上是串行的,中间还需要加上线程上下文切换的开销。如何在取队列中元素时进一步提高并发度(就像ConcurrentHashMap一样只锁部分)。
ConcurrentHashMap是固定的16个段,并且每个段的操作是独立的,所以每个段使用了一把锁,关于这点也是考虑到一些开销和安全的问题,
而队列中元素则是可以动态增长的,因为要涉及到队列指针的问题,不是锁单独一个元素就能够保证其原子性的。这时传说中的非阻塞算法就是比较好的选择了。
非阻塞算法
在《Concurrency in practice》中对两个概念nonblocking和lock-free进行了解释。nonblocking定义为:任何线程失败或挂起不影响其他线程的失败或挂起;
而lock-free定义为:在执行的的每一步,都有线程能够向前推进。而一个基于CAS(compareAndSet)且构造正确的算法一定是nonblocking和lock-free的。
对于java中的非阻塞算法,核心原理是采用硬件级的指令来保证CAS的原子性,不同于lock这样的悲观锁定,非阻塞算法是乐观的,
它基于某些算法步骤是不安全的,在每次进行CAS时可能成功,也可能失败,失败则再取新值重新CAS,
这样不用每次使用lock以保证得到锁的线程必须成功。
一个比较好的例子是Java 理论与实践: 非阻塞算法简介中的Nonblocking stack,这里采用的是Treiber 的非阻塞算法。
这个例子比较容易,之后有一个对ConcurrentLinkedQueue的put方法介绍的例子,这里又是采用的Michael-Scott算法。
开发非阻塞算法是一项非常有挑战的任务,对一个算法中的每一步都需要证明不会产生冲突和死锁。
当然,也遵循一些规律,首先无论是否在多线程的多步执行中必须使得数据结构总是在一致的状态。
即一个线程不能打断另一个线程的原子操作。其次,假设一个线程执行更新,另一个线程等待更新,
如果前一个线程更新失败,则后一个线程会浪费等待时间,并且在等待中没有任何向前推进。
解决的办法是细化原子操作的粒度,并且后一个线程使用快照。
@ThreadSafe
public class LinkedQueue <E> {
private static class Node <E> {
final E item;
final AtomicReference<Node<E>> next;
public Node(E item, Node<E> next) {
this.item = item;
this.next = new AtomicReference<Node<E>>(next);
}
}
private final Node<E> dummy = new Node<E>(null, null);
private final AtomicReference<Node<E>> head
= new AtomicReference<Node<E>>(dummy);
private final AtomicReference<Node<E>> tail
= new AtomicReference<Node<E>>(dummy);
public boolean put(E item) {
Node<E> newNode = new Node<E>(item, null);
while (true) {
Node<E> curTail = tail.get();
Node<E> tailNext = curTail.next.get();
if (curTail == tail.get()) {
if (tailNext != null) {
tail.compareAndSet(curTail, tailNext);
} else {
if (curTail.next.compareAndSet(null, newNode)) {
tail.compareAndSet(curTail, newNode);
return true;
}
}
}
}
}
}
注意1:这里的 compareAndSet是AtomicReference的方法。
全名为java.util.concurrent.atomic.AtomicReference<V>
在文档对其描述如下
compareAndSet
public final boolean compareAndSet(V expect,
V update)如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
参数:
expect - 预期值
update - 新值
返回:
如果成功,则返回 true。返回 false 指示实际值与预期值不相等。
注意2:java.util.concurrent.atomic 包中提供了原子变量的 9 种风格( AtomicInteger; AtomicLong; AtomicReference; AtomicBoolean;
原子整型;长型;引用;及原子标记引用和戳记引用类的数组形式,其原子地更新一对值)。
原子变量类可以认为是 volatile 变量的泛化,它扩展了可变变量的概念,来支持原子条件的比较并设置更新。
注意3:关于ConcurrentLinkedQueue的原理更多可参考《ConcurrentLinkedQueue原理(上)》
注意4:关于ConcurrentLinkedQueue的API介绍可参考《ConcurrentLinkedQueue》
注意5:关于非阻塞算法简介的介绍可参考《Java非阻塞算法简介》和《流行的原子》
而ConcurrentLinkedQueue这个类采用了另一种提高并发度的方式:非阻塞算法(Non-blocking),第一次实现了无锁的并发
谈到这里,先要介绍一下非阻塞算法。其实非阻塞算法并不是什么神秘高深的东西,它需要有一套硬件和指令的配合(似乎目前大多数pc都能支持),
主要解决的问题是:在许多时候,一个线程A持有其他线程B,C,D所需要的资源,但线程A遭遇网络阻塞,或数据库连接阻塞,或页面阻塞等。
这时B,C,D就必须等待 A执行结束才能继续向前推进。这种情况在队列、堆栈等数据结构中也会经常出现,典型的如将一个队列中的数据取出来需要锁整个队列,
也就是说,在对队列的操作中,各个线程实际上是串行的,中间还需要加上线程上下文切换的开销。如何在取队列中元素时进一步提高并发度(就像ConcurrentHashMap一样只锁部分)。
ConcurrentHashMap是固定的16个段,并且每个段的操作是独立的,所以每个段使用了一把锁,关于这点也是考虑到一些开销和安全的问题,
而队列中元素则是可以动态增长的,因为要涉及到队列指针的问题,不是锁单独一个元素就能够保证其原子性的。这时传说中的非阻塞算法就是比较好的选择了。
非阻塞算法
在《Concurrency in practice》中对两个概念nonblocking和lock-free进行了解释。nonblocking定义为:任何线程失败或挂起不影响其他线程的失败或挂起;
而lock-free定义为:在执行的的每一步,都有线程能够向前推进。而一个基于CAS(compareAndSet)且构造正确的算法一定是nonblocking和lock-free的。
对于java中的非阻塞算法,核心原理是采用硬件级的指令来保证CAS的原子性,不同于lock这样的悲观锁定,非阻塞算法是乐观的,
它基于某些算法步骤是不安全的,在每次进行CAS时可能成功,也可能失败,失败则再取新值重新CAS,
这样不用每次使用lock以保证得到锁的线程必须成功。
一个比较好的例子是Java 理论与实践: 非阻塞算法简介中的Nonblocking stack,这里采用的是Treiber 的非阻塞算法。
这个例子比较容易,之后有一个对ConcurrentLinkedQueue的put方法介绍的例子,这里又是采用的Michael-Scott算法。
开发非阻塞算法是一项非常有挑战的任务,对一个算法中的每一步都需要证明不会产生冲突和死锁。
当然,也遵循一些规律,首先无论是否在多线程的多步执行中必须使得数据结构总是在一致的状态。
即一个线程不能打断另一个线程的原子操作。其次,假设一个线程执行更新,另一个线程等待更新,
如果前一个线程更新失败,则后一个线程会浪费等待时间,并且在等待中没有任何向前推进。
解决的办法是细化原子操作的粒度,并且后一个线程使用快照。
@ThreadSafe
public class LinkedQueue <E> {
private static class Node <E> {
final E item;
final AtomicReference<Node<E>> next;
public Node(E item, Node<E> next) {
this.item = item;
this.next = new AtomicReference<Node<E>>(next);
}
}
private final Node<E> dummy = new Node<E>(null, null);
private final AtomicReference<Node<E>> head
= new AtomicReference<Node<E>>(dummy);
private final AtomicReference<Node<E>> tail
= new AtomicReference<Node<E>>(dummy);
public boolean put(E item) {
Node<E> newNode = new Node<E>(item, null);
while (true) {
Node<E> curTail = tail.get();
Node<E> tailNext = curTail.next.get();
if (curTail == tail.get()) {
if (tailNext != null) {
tail.compareAndSet(curTail, tailNext);
} else {
if (curTail.next.compareAndSet(null, newNode)) {
tail.compareAndSet(curTail, newNode);
return true;
}
}
}
}
}
}
注意1:这里的 compareAndSet是AtomicReference的方法。
全名为java.util.concurrent.atomic.AtomicReference<V>
在文档对其描述如下
compareAndSet
public final boolean compareAndSet(V expect,
V update)如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
参数:
expect - 预期值
update - 新值
返回:
如果成功,则返回 true。返回 false 指示实际值与预期值不相等。
注意2:java.util.concurrent.atomic 包中提供了原子变量的 9 种风格( AtomicInteger; AtomicLong; AtomicReference; AtomicBoolean;
原子整型;长型;引用;及原子标记引用和戳记引用类的数组形式,其原子地更新一对值)。
原子变量类可以认为是 volatile 变量的泛化,它扩展了可变变量的概念,来支持原子条件的比较并设置更新。
注意3:关于ConcurrentLinkedQueue的原理更多可参考《ConcurrentLinkedQueue原理(上)》
注意4:关于ConcurrentLinkedQueue的API介绍可参考《ConcurrentLinkedQueue》
注意5:关于非阻塞算法简介的介绍可参考《Java非阻塞算法简介》和《流行的原子》
- ConcurrentLinkedQueue原理(下)
- ConcurrentLinkedQueue原理(下)
- ConcurrentLinkedQueue原理
- ConcurrentLinkedQueue原理(上)
- ConcurrentLinkedQueue原理(上)
- ConcurrentLinkedQueue原理分析
- ConcurrentLinkedQueue原理(上)
- ConcurrentLinkedQueue的实现原理分析
- ConcurrentLinkedQueue的实现原理分析
- ConcurrentLinkedQueue的实现原理分析
- ConcurrentLinkedQueue的实现原理分析
- ConcurrentLinkedQueue的实现原理分析
- ConcurrentLinkedQueue的实现原理分析
- ConcurrentLinkedQueue 的实现原理分析
- ConcurrentLinkedQueue的实现原理分析
- ConcurrentLinkedQueue的实现原理分析
- ConcurrentLinkedQueue
- ConcurrentLinkedQueue
- ConcurrentLinkedQueue原理(上)
- 1+N
- (转)SQL 优化原则
- SqlServer 判断字符是否汉字或数字方法
- android JNI编程技巧
- ConcurrentLinkedQueue原理(下)
- 初始化参数REMOTE_OS_AUTHENT
- 五险一金
- 即时通讯开发平台的使用经验
- c++遍历某个路径下的所有文件
- 获取SP运行花费时间最长前10位/TSQL语句已经运行了多久
- Java包的命名与划分
- 注册gmail邮箱收不到手机验证码怎么办?
- easyui中combobox的值改变onchang事件