这一篇的内容主要来自于《java并发编程实战》,有一说一,看这种写的很专业的书不是很轻松,也没办法直接提升多少开发的能力,可是却能更加夯实基础,就像玩war3,熟练的基本功并不能让你快速地与对方拉开差距,可是却能再每一次团战中积累优点。java
近年来,并发编程的领域更多的偏向于使用非阻塞算法,这种算法底层用原子机器指令(如比较交换CAS之类的)来替代锁用以确保数据在并发访问中的一致性。这样的非阻塞算法普遍的用于在操做系统和JVM中实现线程/程序调用机制、垃圾回收算法等。算法
java5.0后,使用原子变量类(例如AtomicInteger和AtomicReference)来构建高效的非阻塞算法。编程
与基本类型的包装类相比原子变量类是可修改的,在原子变量类中没有从新定义hashCode或equals方法,每一个实例都是不一样的,不宜用作基于散列的容器中的键值。安全
原子变量类比锁的粒度更细量级更轻,将发生竞争的范围缩小到单个变量上。并发
从《并发编程实战》这本书出发,对给予的用例进行测试,可以得出的结论:原子变量因其使用CAS的方法,在性能上有很大优点。性能
以前的文章已经讲过了volatitle变量、CAS算法、AtomicInteger线程安全的原子变量等,没有看过或者已经忘记的的同窗能够点击下方的蓝色连接去看看(复习一下)。测试
若是一个线程的失败或者挂起不会致使其余线程的失败或挂起,这种算法就叫作非阻塞算法。操作系统
在并发访问的环境下,push和pop方法经过CAS算法能够保证栈的原子性和可见性,从而安全高效的更新非阻塞栈。.net
//根据《并发编程实战》的代码进行分析 public class CocurrentStack<E> { /** * AtomicReference和AtomicInteger很是相似,不一样之处就在于AtomicInteger是对整数的封装, * 而AtomicReference则对应普通的对象引用。也就是它能够保证你在修改对象引用时的线程安全性 */ AtomicReference<Node<E>> top = new AtomicReference<Node<E>>(); /* * 这里定义了一个栈(实际上是列表,可是咱们提供的功能仅仅能做为栈), * 当有新值加入,会把旧值挂在新值的next方法上,,能够经过递归next拿到全部Node * */ public void push(E item) { Node<E> newHead = new Node<E>(item); Node<E> oldHead; do { oldHead = top.get(); newHead.next = oldHead; //这边用了CAS算法进行判断,这也是非阻塞算的和核心之一 } while (!top.compareAndSet(oldHead, newHead)); } /** * 实现出栈功能,同时出栈也实现了CAS的功能 */ 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; private Node(E item) { this.item = item; } } }
根据代码咱们能够看出它拥有非阻塞算法的特色:一个线程的失败或者挂起不会致使其余线程的失败或挂起。若是某项操做的完成具备不肯定性,不成功则会从新执行。
这个非阻塞栈经过CAS来尝试修改栈顶元素,该方法为原子操做,若是发现被其余线程干扰,CAS会更新失败,这个时候意味着另外一个线程已经修改了堆栈。这些操做都是原子化地进行的。同时,咱们须要不断的循环,以保证在线程冲突的时候可以重试更新。
根据CAS算法的内容与对非阻塞栈的研究,咱们知道要实现非阻塞算法的方法就是实现原子级的变量。
使用非阻塞算法实现一个连接队列比栈更复杂,由于它须要支持首尾的快速访问,须要维护两个独立的队首指针和队尾指针,初始时都指向队列的末尾节点,在成功加入新元素时两个指针都须要原子化的更新。
//依然是根据《并发编程实战》的代码进行分析 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); //声明AtomicReference类型的头尾节点、一切都是为了安全 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; } } } } } }
经过代码,咱们能看出,队列里的每个节点都有一个空置节点。任何线程在执行插入操做的时候,都可以经过tail.next操做检查队列的状态。若是不为空的状况,则能判断出有其余的线程已经插入了一个节点,可是尚未将tail指向最新的节点,这时候代码能够经过推动tail指针向前移动一个节点把状态恢复为稳定(即尾结点的置空状态)。同时,另外一个已经执行一半的线程的尾结点恢复稳定后,也不会受到影响。
这种设计的好处在于,若是多个线程同时操做方法,不须要加锁等待,每次插入以前链表自身会检查tail.next是否为空来断定队列是否须要保持稳定状态,若是是,它首先会推动队尾指针(可能屡次),直到队列处于稳定状态(tail.next为空)。
咱们从源码中也能看到,非阻塞链表ConcurrentLinkedQueue的实现方式。
非阻塞算法经过使用底层的并发原语,好比CAS算法,取代了锁.原子变量类向用户提供了这些低层级原语,也可以当作"更佳的volatile变量"使用,同时提供了整数类和对象引用的原子化更新操做.
非阻塞算法在设计和实现中很困难,可是在典型条件下可以提供更好的可伸缩性,并能更好地预防活跃度失败。从JVM并发性能的提高很大程度上来源于非阻塞算法的使用,包括在JVM内部以及平台类库。
有须要的同窗能够加个人公众号,之后的最新的文章第一时间都在里面,也能够找我要思惟导图