Java并发编程实战 第15章 原子变量和非阻塞同步机制

非阻塞的同步机制

简单的说,那就是又要实现同步,又不使用锁。

与基于锁的方案相比,非阻塞算法的实现要麻烦的多,但是它的可伸缩性和活跃性上拥有巨大的优势。

实现非阻塞算法的常见方法就是使用volatile语义和原子变量。

硬件对并发的支持

原子变量的产生主要是处理器的支持,最重要的是大多数处理器架构都支持的CAS(比较并交换)指令。

模拟实现AtomicInteger的++操作

首先我们模拟处理器的CAS语法,之所以说模拟,是因为CAS在处理器中是原子操作直接支持的。不需要加锁。

  1. public synchronized int compareAndSwap(int exceptValue, int newValue){
  2.         int oldValue = count;
  3.         if(oldValue == exceptValue){
  4.             count = newValue;
  5.         }
  6.         return oldValue;
  7.     }

注意:CAS总是返回oldValue。

使用上面的方法模拟AtomicInteger的++操作。

  1. class MyAtomicInteger{
  2.    private int value;
  3.    public int incrementAndGet()
  4.    {
  5.       int v ;
  6.       do{
  7.          v = value;
  8.       }while(v != compareAndSwap(v,v+1));
  9.  
  10.       return v + 1;
  11.    }
  12. }

注意:Java的AtomicInteger大概实现机制就是这样的,不会阻塞,使用处理器的CAS功能,但是要轮询尝试。

看起来轮询尝试性能会更差,其实不然,当竞争不是非常高的时候,基于CAS的算法更胜一筹。

原子类

AtomicBoolean AtomicInteger AtomicLong AtomicReference

原子数组类:AtomicIntegerArray AtomicLong AtomicReferenceArray。

使用volatile语法修饰的数组只能保证数组变量本身的volatile语义,不能保证元素的volatile语义。这个时候应该使用,原子数组类。

注意:AtomicBoolean AtomicInteger AtomicLong和非原子的对应数值类如Integer截然不用。实现机制完全不一样,也没有对应关系。最重要的一个差别:这个三个原子类是可变的。而且是使用的Object的hashCode和equals方法,没有自己扩展。

性能比较:锁与原子变量

在中低程度的竞争下,原子变量能提供很高的可伸缩性,原子变量性能超过锁;而在高强度的竞争下,锁能够更有效地避免竞争,锁的性能将超过原子变量的性能。但在更真实的实际情况(一般没有那么高强大的竞争)中,原子变量的性能将超过锁的性能。

注意:不论是锁还是原子变量,都远远比不上避免共享状态(如使用线程封闭技术,但是使用场景计较局限),彻底消除竞争的效率。

两个非阻塞的算法示例

  1. /**
  2.  * 使用Treiber算法构造的非阻塞栈
  3.  */
  4. public class ConcurrentStack<E> {
  5.     private AtomicReference<Node<E>> top = new AtomicReference<ConcurrentStack.Node<E>>();
  6.  
  7.     public void push(E item){
  8.         Node<E> newHead = new Node<E>(item);
  9.         Node<E> oldHead;
  10.  
  11.         do{
  12.             oldHead = top.get();
  13.             newHead.next = oldHead;
  14.         } while (!top.compareAndSet(oldHead, newHead));
  15.     }
  16.  
  17.     public E pop(){
  18.         Node<E> oldHead;
  19.         Node<E> newHead;
  20.  
  21.         do {
  22.             oldHead = top.get();
  23.             if (oldHead == null)
  24.                 return null;
  25.             newHead = oldHead.next;
  26.         } while (!top.compareAndSet(oldHead, newHead));
  27.         return oldHead.item;
  28.     }
  29.  
  30.     private static class Node<E>{
  31.         public final E item;
  32.         public Node<E> next;
  33.  
  34.         public Node(E item){
  35.             this.item = item;
  36.         }
  37.     }
  38. }

下面这个没有看懂,留在这里记录,以后再看:

  1. /**
  2.  * 链表中非阻塞算法中的插入排序,来自Michael-Scott
  3.  */
  4. public class LinkedQueue<E> {
  5.     private static class Node<E>{
  6.         final E item;
  7.         final AtomicReference<Node<E>> next;
  8.  
  9.         public Node(E item, Node<E> next){
  10.             this.item = item;
  11.             this.next = new AtomicReference<>(next);
  12.         }
  13.     }
  14.  
  15.     private final Node<E> dummy = new Node<E>(null, null);
  16.     private final AtomicReference<Node<E>> head =
  17.                         new AtomicReference<>(dummy);
  18.     private final AtomicReference<Node<E>> tail =
  19.                         new AtomicReference<>(dummy);
  20.  
  21.     public boolean put(E item){
  22.         Node<E> newNode = new Node<E>(item, null);
  23.         while (true){
  24.             Node<E> curTail = tail.get();
  25.             Node<E> tailNext = curTail.next.get();
  26.             if (curTail == tail.get()){ //尾部还未修改
  27.                 if (tailNext != null){
  28.                     // 队列处于中间状态(即新节点已经接上,尾节点还未更新),推进尾节点
  29.                     tail.compareAndSet(curTail, tailNext);
  30.                 } else{
  31.                     // 处于稳定状态, 尝试插入新节点
  32.                     if (curTail.next.compareAndSet(null, newNode)){
  33.                         // 插入成功后,推进尾节点
  34.                         tail.compareAndSet(curTail, tailNext);
  35.                         return true;
  36.                     }
  37.                 }
  38.             }
  39.         }
  40.     }
  41. }
时间: 2024-12-29 07:27:48

Java并发编程实战 第15章 原子变量和非阻塞同步机制的相关文章

《Java并发编程实战》第十五章 原子变量与非阻塞同步机制 读书笔记

一.锁的劣势 锁定后如果未释放,再次请求锁时会造成阻塞,多线程调度通常遇到阻塞会进行上下文切换,造成更多的开销. 在挂起与恢复线程等过程中存在着很大的开销,并且通常存在着较长时间的中断. 锁可能导致优先级反转,即使较高优先级的线程可以抢先执行,但仍然需要等待锁被释放,从而导致它的优先级会降至低优先级线程的级别. 二.硬件对并发的支持 处理器填写了一些特殊指令,例如:比较并交换.关联加载/条件存储. 1 比较并交换 CAS的含义是:"我认为V的值应该为A,如果是,那么将V的值更新为B,否则不需要修

第十五章 原子变量和非阻塞同步机制

1.非阻塞算法 如果在算法中,一个线程的失败或挂起不会导致其他线程也失败或挂起,那么这种算法就称为非阻塞算法.如果这种算法的每个步骤中都存在某个线程能够执行下去,那么这种算法也称为无锁算法. 这种算法利用底层的原子机器指令代替锁来确保数据在并发访问中的一致性. 2.硬件对并发的支持 2.1 CAS(Compare-and-Swap) 包含3个操作数--需要读写的内存位置.进行比较的值A和拟写入的新值B.当且仅当V的值等于A时,CAS才会通过原子的方式用新值B更新V的值.无论位置V的值是否等于A,

并发编程 20—— 原子变量和非阻塞同步机制

并发编程 01—— ConcurrentHashMap 并发编程 02—— 阻塞队列和生产者-消费者模式 并发编程 03—— 闭锁CountDownLatch 与 栅栏CyclicBarrier 并发编程 04—— Callable和Future 并发编程 05—— CompletionService : Executor 和 BlockingQueue 并发编程 06—— 任务取消 并发编程 07—— 任务取消 之 中断 并发编程 08—— 任务取消 之 停止基于线程的服务 并发编程 09——

java并发编程11.原子变量与非阻塞同步机制

在非阻塞算法中不存在死锁和其他活跃性问题. 在基于锁的算法中,如果一个线程在休眠或自旋的同时持有一个锁,那么其他线程都无法执行下去,而非阻塞算法不会受到单个线程失败的影响. 锁的劣势 许多JVM都对非竞争锁获取和释放操作进行了极大的优化,但如果有多个线程同时请求锁,那么JVM就需要借助操作系统地功能.如果出现了这种情况,那么一些线程将被挂起并且在稍后恢复运行.当线程恢复执行时,必须等待其他线程执行完它们的时间片以后,才能被调度执行.在挂起和恢复线程等过程中存在着很大的开销,并且通常存在着较大时间

多线程并发编程之原子变量与非阻塞同步机制

1.非阻塞算法 非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 —— 例如比较和交换.非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞吐率,对生存问题(例如死锁和优先级反转)也能提供更好的防御.使用底层的原子化机器指令取代锁,比如比较并交换(CAS,compare-and-swap). 2.悲观技术 独占锁是一种悲观的技术.它假设最坏的情况发生(如果不加锁,其它线程会破坏对象状态),即使没有发生最坏的情况,仍然用锁保护对象状态.

《Java并发编程实战》第二章 线程安全性 读书笔记

一.什么是线程安全性 编写线程安全的代码 核心在于要对状态访问操作进行管理. 共享,可变的状态的访问 - 前者表示多个线程访问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与其规范完全一致. 多个线程同时操作共享的变量,造成线程安全性问题. * 编写线程安全性代码的三种方法: 不在线程之间共享该状态变量 将状态变量修改为不可变的变量 在访问状态变量时使用同步 Java同步机制工具: synchronized volatile类型变量 显示锁(Explicit Lock

Java并发编程实战 第16章 Java内存模型

什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Before.两个操作缺乏Happens-Before关系,则Jvm会对它们进行任意的重排序. Happends-Before的规则包括: 1. 程序顺序规则.若程序中操作A在操作B之前,则线程中操作A在操作B之前执行. 2. 监视器锁规则.在同一监视器锁上的解锁操作必须在加锁操作之前执行.如图所示,

JAVA并发编程实战---第三章:对象的共享(2)

线程封闭 如果仅仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭,它是实现线程安全性的最简单的方式之一.当某个对象封闭在一个线程中时,这种方法将自动实现线程安全性,即使被封闭的对象本生不是线程安全的. 实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发.避免并发最简单的方法就是线程封闭.什么是线程封闭呢? 就是把对象封装到一个线程里,只有这一个线程能看到此对象.那么这个对象就算不是线程安全的也不会出现任何安全问题.实现线程封闭有哪些方法呢? 1:ad-hoc线程封闭 这是完全靠

JAVA并发编程实战---第三章:对象的共享

在没有同步的情况下,编译器.处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整.在缺乏足够同步的多线程程序中,要对内存操作的执行顺序进行判断几乎无法得到正确的结果. 非原子的64位操作 当线程在没有同步的情况下读取变量时,可能会读到一个失效值,但至少这个值是由之前的某个线程设置,而不是一个随机值.这种安全性保证也被称为最低安全性. Java内存模型要求:变量的读取操作和写入操作都必须是原子操作,但对于非Volatile类型的long和Double变量,JVM允许将64的读操作或写操作