ConcurrentHashMap,它是一个以Concurrent开头的并发集合类,其原理是通过增加锁和细化锁的粒度来提高并发度。
而ConcurrentLinkedQueue这个类采用了另一种提高并发度的方式:非阻塞算法(Non-blocking),第一次实现了无锁的并发<wbr style="line-height:25px">。</wbr>
谈到这里,先要介绍一下非阻塞算法。其实非阻塞算法并不是什么神秘高深的东西,它需要有一套硬件和指令的配合(似乎目前大多数pc都能支持),
主要解决的问题是:在许多时候,一个线程A持有其他线程B,C,D所需要的资源,但线程A遭遇网络阻塞,或数据库连接阻塞,或页面阻塞等。
这时B,C,D就必须等待A执行结束才能继续向前推进。这种情况在队列、堆栈等数据结构中也会经常出现,典型的如将一个队列中的数据取出来需要锁整个队列,
也就是说,在对队列的操作中,各个线程实际上是串行的,中间还需要加上线程上下文切换的开销。如何在取队列中元素时进一步提高并发度(就像ConcurrentHashMap一样只锁部分)。
ConcurrentHashMap是固定的16个段,并且每个段的操作是独立的,所以每个段使用了一把锁,关于这点也是考虑到一些开销和安全的问题,
而队列中元素则是可以动态增长的,因为要涉及到队列指针的问题,不是锁单独一个元素就能够保证其原子性的。这时传说中的非阻塞算法就是比较好的选择了。
非阻塞算法
在《Concurrencyinpractice》中对两个概念nonblocking和lock-free进行了解释。nonblocking定义为:任何线程失败或挂起不影响其他线程的失败或挂起;
而lock-free定义为:在执行的的每一步,都有线程能够向前推进。而一个基于CAS(compareAndSet)且构造正确的算法一定是nonblocking和lock-free的。
对于java中的非阻塞算法,核心原理是采用硬件级的指令来保证CAS的原子性,不同于lock这样的悲观锁定,非阻塞算法是乐观的,
它基于某些算法步骤是不安全的,在每次进行CAS时可能成功,也可能失败,失败则再取新值重新CAS,
这样不用每次使用lock以保证得到锁的线程必须成功。
一个比较好的例子是Java理论与实践:非阻塞算法简介中的Nonblockingstack,这里采用的是Treiber的非阻塞算法。
这个例子比较容易,之后有一个对ConcurrentLinkedQueue的put方法介绍的例子,这里又是采用的Michael-Scott算法。
开发非阻塞算法是一项非常有挑战的任务,对一个算法中的每一步都需要证明不会产生冲突和死锁。
当然,也遵循一些规律,首先无论是否在多线程的多步执行中必须使得数据结构总是在一致的状态。
即一个线程不能打断另一个线程的原子操作。其次,假设一个线程执行更新,另一个线程等待更新,
如果前一个线程更新失败,则后一个线程会浪费等待时间,并且在等待中没有任何向前推进。
解决的办法是细化原子操作的粒度,并且后一个线程使用快照。
@ThreadSafe
publicclassLinkedQueue<E>{
privatestaticclassNode<E>{
finalEitem;
finalAtomicReference<Node<E>>next;
publicNode(Eitem,Node<E>next){
this.item=item;
this.next=newAtomicReference<Node<E>>(next);
}
}
privatefinalNode<E>dummy=newNode<E>(null,null);
privatefinalAtomicReference<Node<E>>head
=newAtomicReference<Node<E>>(dummy);
privatefinalAtomicReference<Node<E>>tail
=newAtomicReference<Node<E>>(dummy);
publicbooleanput(Eitem){
Node<E>newNode=newNode<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);
returntrue;
}
}
}
}
}
}
注意1:这里的compareAndSet是AtomicReference的方法。
全名为java.util.concurrent.atomic.AtomicReference<V>
在文档对其描述如下
compareAndSet
publicfinalbooleancompareAndSet(Vexpect,
Vupdate)如果当前值==预期值,则以原子方式将该值设置为给定的更新值。
参数:
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),第一次实现了无锁的并发<wbr style="line-height:25px">。</wbr>
谈到这里,先要介绍一下非阻塞算法。其实非阻塞算法并不是什么神秘高深的东西,它需要有一套硬件和指令的配合(似乎目前大多数pc都能支持),
主要解决的问题是:在许多时候,一个线程A持有其他线程B,C,D所需要的资源,但线程A遭遇网络阻塞,或数据库连接阻塞,或页面阻塞等。
这时B,C,D就必须等待A执行结束才能继续向前推进。这种情况在队列、堆栈等数据结构中也会经常出现,典型的如将一个队列中的数据取出来需要锁整个队列,
也就是说,在对队列的操作中,各个线程实际上是串行的,中间还需要加上线程上下文切换的开销。如何在取队列中元素时进一步提高并发度(就像ConcurrentHashMap一样只锁部分)。
ConcurrentHashMap是固定的16个段,并且每个段的操作是独立的,所以每个段使用了一把锁,关于这点也是考虑到一些开销和安全的问题,
而队列中元素则是可以动态增长的,因为要涉及到队列指针的问题,不是锁单独一个元素就能够保证其原子性的。这时传说中的非阻塞算法就是比较好的选择了。
非阻塞算法
在《Concurrencyinpractice》中对两个概念nonblocking和lock-free进行了解释。nonblocking定义为:任何线程失败或挂起不影响其他线程的失败或挂起;
而lock-free定义为:在执行的的每一步,都有线程能够向前推进。而一个基于CAS(compareAndSet)且构造正确的算法一定是nonblocking和lock-free的。
对于java中的非阻塞算法,核心原理是采用硬件级的指令来保证CAS的原子性,不同于lock这样的悲观锁定,非阻塞算法是乐观的,
它基于某些算法步骤是不安全的,在每次进行CAS时可能成功,也可能失败,失败则再取新值重新CAS,
这样不用每次使用lock以保证得到锁的线程必须成功。
一个比较好的例子是Java理论与实践:非阻塞算法简介中的Nonblockingstack,这里采用的是Treiber的非阻塞算法。
这个例子比较容易,之后有一个对ConcurrentLinkedQueue的put方法介绍的例子,这里又是采用的Michael-Scott算法。
开发非阻塞算法是一项非常有挑战的任务,对一个算法中的每一步都需要证明不会产生冲突和死锁。
当然,也遵循一些规律,首先无论是否在多线程的多步执行中必须使得数据结构总是在一致的状态。
即一个线程不能打断另一个线程的原子操作。其次,假设一个线程执行更新,另一个线程等待更新,
如果前一个线程更新失败,则后一个线程会浪费等待时间,并且在等待中没有任何向前推进。
解决的办法是细化原子操作的粒度,并且后一个线程使用快照。
@ThreadSafe
publicclassLinkedQueue<E>{
privatestaticclassNode<E>{
finalEitem;
finalAtomicReference<Node<E>>next;
publicNode(Eitem,Node<E>next){
this.item=item;
this.next=newAtomicReference<Node<E>>(next);
}
}
privatefinalNode<E>dummy=newNode<E>(null,null);
privatefinalAtomicReference<Node<E>>head
=newAtomicReference<Node<E>>(dummy);
privatefinalAtomicReference<Node<E>>tail
=newAtomicReference<Node<E>>(dummy);
publicbooleanput(Eitem){
Node<E>newNode=newNode<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);
returntrue;
}
}
}
}
}
}
注意1:这里的compareAndSet是AtomicReference的方法。
全名为java.util.concurrent.atomic.AtomicReference<V>
在文档对其描述如下
compareAndSet
publicfinalbooleancompareAndSet(Vexpect,
Vupdate)如果当前值==预期值,则以原子方式将该值设置为给定的更新值。
参数:
expect-预期值
update-新值
返回:
如果成功,则返回true。返回false指示实际值与预期值不相等。
注意2:java.util.concurrent.atomic包中提供了原子变量的9种风格(AtomicInteger;AtomicLong;AtomicReference;AtomicBoolean;
原子整型;长型;引用;及原子标记引用和戳记引用类的数组形式,其原子地更新一对值)。
原子变量类可以认为是volatile变量的泛化,它扩展了可变变量的概念,来支持原子条件的比较并设置更新。
注意3:关于ConcurrentLinkedQueue的原理更多可参考《ConcurrentLinkedQueue原理(上)》
注意4:关于ConcurrentLinkedQueue的API介绍可参考《ConcurrentLinkedQueue》
注意5:关于非阻塞算法简介的介绍可参考《Java非阻塞算法简介》和《流行的原子》