`

Java多线程(二)之Atomic:原子变量与原子类

 
阅读更多

一、何谓Atomic?

 

 Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位。计算机中的Atomic是指不能分割成若干部分的意思。如果一段代码被认为是Atomic,则表示这段代码在执行过程中,是不能被中断的。通常来说,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成)

 

 在x86 平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。

 

、java.util.concurrent中的原子变量

 

 

 

无论是直接的还是间接的,几乎 java.util.concurrent 包中的所有类都使用原子变量,而不使用同步。类似 ConcurrentLinkedQueue 的类也使用原子变量直接实现无等待算法,而类似 ConcurrentHashMap 的类使用 ReentrantLock 在需要时进行锁定。然后, ReentrantLock 使用原子变量来维护等待锁定的线程队列。

如果没有 JDK 5.0 中的 JVM 改进,将无法构造这些类,这些改进暴露了(向类库,而不是用户类)接口来访问硬件级的同步原语。然后,java.util.concurrent 中的原子变量类和其他类向用户类公开这些功能

 

java.util.concurrent.atomic的原子类

 

这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。其中的类可以分成4组

  • AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • AtomicIntegerArray,AtomicLongArray
  • AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray

其中AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference是类似的。

首先AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference内部api是类似的:举个AtomicReference的例子

 

使用AtomicReference创建线程安全的堆栈

 

Java代码  收藏代码
  1. public class LinkedStack<T> {  
  2.   
  3.     private AtomicReference<Node<T>> stacks = new AtomicReference<Node<T>>();  
  4.   
  5.     public T push(T e) {  
  6.         Node<T> oldNode, newNode;  
  7.         while (true) { //这里的处理非常的特别,也是必须如此的。  
  8.             oldNode = stacks.get();  
  9.             newNode = new Node<T>(e, oldNode);  
  10.             if (stacks.compareAndSet(oldNode, newNode)) {  
  11.                 return e;  
  12.             }  
  13.         }  
  14.     }  
  15.       
  16.     public T pop() {  
  17.         Node<T> oldNode, newNode;  
  18.         while (true) {  
  19.             oldNode = stacks.get();  
  20.             newNode = oldNode.next;  
  21.             if (stacks.compareAndSet(oldNode, newNode)) {  
  22.                 return oldNode.object;  
  23.             }  
  24.         }  
  25.     }  
  26.   
  27.     private static final class Node<T> {  
  28.         private T object;  
  29.           
  30.         private Node<T> next;  
  31.   
  32.         private Node(T object, Node<T> next) {  
  33.             this.object = object;  
  34.             this.next = next;  
  35.         }  
  36.     }  
  37. }  
  

 

然后关注字段的原子更新。

AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T,V>是基于反射的原子更新字段的值。

相应的API也是非常简单的,但是也是有一些约束的。

(1)字段必须是volatile类型的!volatile到底是个什么东西。请查看 http://blog.csdn.net/a511596982/article/details/8201744

(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。

(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。

(5)对于AtomicIntegerFieldUpdaterAtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater

 

在下面的例子中描述了操作的方法。

 

[java] view plaincopy
 
  1. import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;   
  2.   
  3. public class AtomicIntegerFieldUpdaterDemo {   
  4.   
  5.    class DemoData{  
  6.        public volatile int value1 = 1;  
  7.        volatile int value2 = 2;  
  8.        protected volatile int value3 = 3;  
  9.        private volatile int value4 = 4;  
  10.    }  
  11.     AtomicIntegerFieldUpdater<DemoData> getUpdater(String fieldName) {  
  12.         return AtomicIntegerFieldUpdater.newUpdater(DemoData.class, fieldName);  
  13.     }  
  14.     void doit() {  
  15.         DemoData data = new DemoData();  
  16.         System.out.println("1 ==> "+getUpdater("value1").getAndSet(data, 10));  
  17.         System.out.println("3 ==> "+getUpdater("value2").incrementAndGet(data));  
  18.         System.out.println("2 ==> "+getUpdater("value3").decrementAndGet(data));  
  19.         System.out.println("true ==> "+getUpdater("value4").compareAndSet(data, 45));  
  20.     }  
  21.     public static void main(String[] args) {  
  22.         AtomicIntegerFieldUpdaterDemo demo = new AtomicIntegerFieldUpdaterDemo();  
  23.         demo.doit();  
  24.     }  
  25. }   

 


在上面的例子中DemoData的字段value3/value4对于AtomicIntegerFieldUpdaterDemo类是不可见的,因此通过反射是不能直接修改其值的。

 

AtomicMarkableReference类描述的一个<Object,Boolean>的对,可以原子的修改Object或者Boolean的值,这种数据结构在一些缓存或者状态描述中比较有用。这种结构在单个或者同时修改Object/Boolean的时候能够有效的提高吞吐量。

 

AtomicStampedReference类维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。对比AtomicMarkableReference类的<Object,Boolean>,AtomicStampedReference维护的是一种类似<Object,int>的数据结构,其实就是对对象(引用)的一个并发计数。但是与AtomicInteger不同的是,此数据结构可以携带一个对象引用(Object),并且能够对此对象和计数同时进行原子操作。

在本文结尾会提到“ABA问题”,而AtomicMarkableReference/AtomicStampedReference在解决“ABA问题”上很有用

三、Atomic类的作用

 
  • 使得让对单一数据的操作,实现了原子化
  • 使用Atomic类构建复杂的,无需阻塞的代码
    • 访问对2个或2个以上的atomic变量(或者对单个atomic变量进行2次或2次以上的操作)通常认为是需要同步的,以达到让这些操作能被作为一个原子单元。
  • 无锁定且无等待算法

     

    基于 CAS (compare and swap)的并发算法称为 无锁定算法,因为线程不必再等待锁定(有时称为互斥或关键部分,这取决于线程平台的术语)。无论 CAS 操作成功还是失败,在任何一种情况中,它都在可预知的时间内完成。如果 CAS 失败,调用者可以重试 CAS 操作或采取其他适合的操作。

    如果每个线程在其他线程任意延迟(或甚至失败)时都将持续进行操作,就可以说该算法是 无等待的。与此形成对比的是, 无锁定算法要求仅 某个线程总是执行操作。(无等待的另一种定义是保证每个线程在其有限的步骤中正确计算自己的操作,而不管其他线程的操作、计时、交叉或速度。这一限制可以是系统中线程数的函数;例如,如果有 10 个线程,每个线程都执行一次CasCounter.increment() 操作,最坏的情况下,每个线程将必须重试最多九次,才能完成增加。)

    再过去的 15 年里,人们已经对无等待且无锁定算法(也称为 无阻塞算法)进行了大量研究,许多人通用数据结构已经发现了无阻塞算法。无阻塞算法被广泛用于操作系统和 JVM 级别,进行诸如线程和进程调度等任务。虽然它们的实现比较复杂,但相对于基于锁定的备选算法,它们有许多优点:可以避免优先级倒置和死锁等危险,竞争比较便宜,协调发生在更细的粒度级别,允许更高程度的并行机制等等。

    常见的:

    非阻塞的计数器Counter 
    非阻塞堆栈ConcurrentStack
    非阻塞的链表ConcurrentLinkedQueue

    非阻塞算法简介:http://www.ibm.com/developerworks/cn/java/j-jtp04186/

     

分享到:
评论

相关推荐

    Java多线程Atomic包操作原子变量与原子类详解

    主要介绍了Java多线程Atomic包操作原子变量与原子类详解,简单介绍了Atomic,同时涉及java.util.concurrent中的原子变量,Atomic类的作用等相关内容,具有一定参考价值,需要的朋友可以了解下。

    Java多线程:AtomicIntegerArray 原子更新数组类.docx

    原子更新数组类顾名思义,通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类: AtomicIntegerArray:原子更新整型数组里的元素。 AtomicLongArray:原子更新长整型数组里的元素。 ...

    Sparklingredstar#JavaBooks#Java多线程-Atomic原子类1

    引言JavaGuide :一份涵盖大部分Java程序员所需要掌握的核心知识。star:45159,替他宣传一下子这位大佬,总结的真好!我引用这位大佬的文章,因为

    92道Java多线程与并发面试题含答案(很全)

    Java并发编程的核心概念包括: 线程(Thread):线程是程序执行流的最小单元。...原子操作(Atomic Operations):原子操作是不可中断的操作,即在多线程环境中,这些操作要么完全执行,要么完全不执行。

    scalatomic:原子变量易于使用(很难滥用)的界面

    该库提供的主要数据类型是Atomic[A] ,它表示值为A原子变量。 人们使用newAtomic方法创建了这样的变量: val myAtomicVar : Atomic [ Int ] = newAtomic( 0 ) Atomic对象始终具有一个值。 因此,在创建时,必须...

    Java 高并发多线程编程系列案例代码

    Java 高并发多线程编程系列案例代码 & 教程 & 面试题集锦! !! 包括但不限于线程安全性, atomic包下相关类、CAS原理、Unsafe类、synchronized关键字等的使用及注意事项,

    atomic:原子项目的存储库

    原子 原子项目简介 Atomic 项目提供了一个平台,允许在不同类型的服务器(物理机、虚拟机、云……)上安装和管理容器。 主机旨在尽可能高效地提供容器服务,因此是非常简单的主机,只有必要的软件。 这些主机是从 ...

    Java多线程atomic包介绍及使用方法

    主要介绍了Java多线程atomic包介绍及使用方法,涉及原子更新基本类型介绍及代码示例,具有一定参考价值,需要的朋友可以了解下。

    基础技术部牛路《Java多线程入阶分享》纯干货

    Java多线程入阶干货分享 1.使用线程的经验:设置名称、响应中断、使用ThreadLocal 2.Executor:ExecutorService和Future 3.阻塞队列:put和take、offer和poll、drainTo 4.线程间通信:lock、condition、wait、notify...

    JUC–Atomic原子类

    java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int类型的值...

    queue_atomic:使用C ++ 11原子的多生产者多消费者队列模板

    queue_atomic 使用C ++ 11原子的多生产者多消费者队列模板。 通过将单调递增的版本号打包到队列的前后偏移量中,解决了ABA问题并实现了两阶段有序更新。 通过检查预期的版本计数器在打包的前后偏移量中是否可见来...

    java面试题_多线程(68题).pdf

    1. 什么是线程?...在Java Concurrency API中有哪些原⼦类(atomic classes)? 8. 什么是Executors框架? 9. 什么是阻塞队列?如何使⽤阻塞队列来实现⽣产者-消费者模型? 10. 什么是Callable和Future?

    java线程-Atomic的含义及示例_.docx

    java线程-Atomic的含义及示例_.docx

    【2018最新最详细】并发多线程教程

    24.Java中atomic包中的原子操作类总结 25.大白话说java并发工具类-CountDownLatch,CyclicBarrier 26.大白话说java并发工具类-Semaphore,Exchanger 27.一篇文章,让你彻底弄懂生产者--消费者问题

    atomic:基于流星的Blog应用程序

    原子博客使用Meteor构建的超干净博客应用程序,该博客具有许多功能,包括行政仪表盘帖子注释媒体用户数设定值降价促销帖子... 有关示例,请参见以下软件包: atomic:posts , atomic:categories , atomic:accounts 。

    深入了解Java atomic原子类的使用方法和原理

    主要介绍了深入了解Java atomic原子类的使用方法和原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下

    Java多线程源码笔记.pdf

    1.什么是多线程 2.Thread类解析 3.使用多线程需要注意的问题 4.synchronized锁和lock锁 5.AQS 6.ReentrantLock和ReentrantReadWriteLock 7.线程池 8.死锁 9.线程常用的工具栏 10.Atomic 11.ThreadLocal

    atomic:强制原子访问的同步原子的包装器类型

    原子 用于强制原子访问的原始类型的简单包装器。安装$ go get -u go.uber.org/atomic@v1旧版导入路径从 v1.5.0 开始,导入路径go.uber.org/atomic是唯一支持的使用这个包的方式。 如果您使用的是 Go 模块,则此包将...

    java编发编程:JUC综合讲解

    JUC(java.util.concurrent)库是 Java 标准库的一部分,提供了丰富的多线程并发工具,旨在帮助开发者编写高性能、高可伸缩性的并发程序。下面综合介绍 JUC 库的几个核心概念以及它们在并发编程中的重要性。 1. ...

    JAVA面试必成功之JAVA面试秘籍

    Java多线程,包括多线程基础、synchronized连环问、线程池、AQS、原子类等。Spring,包括Spring基础、bean、IOC、AOP、事务、Spring MVC、Spring Boot等。MySQL,包括MySQL的基础、事务、锁、分库分表、读写分离、...

Global site tag (gtag.js) - Google Analytics