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

时间:2022-11-08 18:11:34

非阻塞的同步机制

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

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

实现非阻塞算法的常见方法就是使用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. }

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

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

    一.锁的劣势 锁定后假设未释放.再次请求锁时会造成堵塞.多线程调度通常遇到堵塞会进行上下文切换,造成很多其它的开销. 在挂起与恢复线程等过程中存在着非常大的开销,而且通常存在着较长时间的中断. 锁可能 ...

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

    1.非阻塞算法 非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 -- 例如比较和交换.非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞 ...

  3. Java并发编程实战---第六章:任务执行

    废话开篇 今天开始学习Java并发编程实战,很多大牛都推荐,所以为了能在并发编程的道路上留下点书本上的知识,所以也就有了这篇博文.今天主要学习的是任务执行章节,主要讲了任务执行定义.Executor. ...

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

    什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...

  5. java并发编程实战:第十五章----原子变量与非阻塞机制

    非阻塞算法:使用底层的原子机器指令(例如比较并交换指令)代替锁来确保数据在并发访问中的一致性 应用于在操作系统和JVM中实现线程 / 进程调度机制.垃圾回收机制以及锁和其他并发数据结构 可伸缩性和活跃 ...

  6. 【java并发编程实战】第一章笔记

    1.线程安全的定义 当多个线程访问某个类时,不管允许环境采用何种调度方式或者这些线程如何交替执行,这个类都能表现出正确的行为 如果一个类既不包含任何域,也不包含任何对其他类中域的引用.则它一定是无状态 ...

  7. java并发编程实战:第二章----线程安全性

    一个对象是否需要是线程安全的取决于它是否被多个线程访问. 当多个线程访问同一个可变状态量时如果没有使用正确的同步规则,就有可能出错.解决办法: 不在线程之间共享该变量 将状态变量修改为不可变的 在访问 ...

  8. Java并发编程实战 第8章 线程池的使用

    合理的控制线程池的大小: 下面内容来自网络.不过跟作者说的一致.不想自己敲了.留个记录. 要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析: 任务的性质:CPU密集型任务.IO ...

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

    一.什么是线程安全性 编写线程安全的代码 核心在于要对状态訪问操作进行管理. 共享,可变的状态的訪问 - 前者表示多个线程訪问, 后者声明周期内发生改变. 线程安全性 核心概念是正确性.某个类的行为与 ...

随机推荐

  1. jquery中的ajax方法参数总是记不住,这里记录一下。

    1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如 ...

  2. CIDR详解和ip最长地址前缀匹配

    1.CIDR是什么 无类域间路由(CIDR)编址方案 摒弃传统的基于类的地址分配方式,允许使用任意长度的地址前缀,有效提高地址空间的利用率. 就是一个ip加一个网络掩码,不过这个掩码不是之前只有3个值 ...

  3. PHP 爬虫

    1.爬虫的本质简单来说,就是读取页面源代码,然后用正则匹配得到想要的数据. 示例如下: private function spider_jiuyou_list($listname,$url)    { ...

  4. oracle执行计划之-表连接方式

    转载自:http://blog.csdn.net/tianlesoftware/article/details/5826546 在多表联合查询的时候,如果我们查看它的执行计划,就会发现里面有多表之间的 ...

  5. 根据IP地址获取地址所在城市帮助类(IPHelper)

    很多类库都是需要在长时间的编写过程中进行积累的,进入软件编程行业已经是第五个年头了,从2011年写下第一行代码到现在不知道已经写了多少行代码了,时间也过得挺快的.最近事情比较多,也很少写博客了,最近项 ...

  6. 在CDialog&colon;&colon;OnInitDialog设置DEFAULT-BUTTON的注意事项

    如果你的Dialog是在资源编辑器里面创建的,那么你首先要去资源编辑器把对应的Button的Default Button选项设置为True 另外,如果你使用GotoDlgCtrl,那么记得OnInit ...

  7. Some SQL basics

    1, Index An index is a set of data pointers stored on disk associated with a single table. The main ...

  8. hive数据导出和常用操作

    导出到本地文件 insert overwrite local directory '/home/hadoop'select * from test1; 导出到hdfs insert overwrite ...

  9. MYSQL区分大小写

    MYSQL区分大小写   1.linux下mysql安装完后是默认:区分表名的大小写,不区分列名的大小写: 2.用root帐号登录后,在/etc/my.cnf 中的[mysqld]后添加添加lower ...

  10. CodeForces 712D Memory and Scores

    $dp$,前缀和. 记$dp[i][j]$表示$i$轮结束之后,两人差值为$j$的方案数. 转移很容易想到,但是转移的复杂度是$O(2*k)$的,需要优化,观察一下可以发现可以用过前缀和来优化. 我把 ...