我们知道,JDK1.5以后引入了并发包(java.util.concurrent)用于解决多CPU时代的并发问题,而并发包中的类大部分是基于Queue的并发类,Queue在大多数情况下使用了原子类(Atomic)操作,因此要了解Concurrent包首先要了解Atomic类。

在很多时候,我们需要的仅仅是一个简单的、高效的、线程安全的递增或者递减方案,这个方案一般需要满足以下要求:

1、  简单:操作简单,底层实现简单

2、  高效:占用资源少,操作速度快

3、  安全:在高并发和多线程环境下要保证数据的正确性

Java 中延续了C++带来的++、--操作但是这个不是线程安全的,在前面JVM内存模型中我们已经提到过,执行i++需要3个步骤:

1、  从内存中取出变量,放到线程拷贝内存中

2、  执行++操作

3、  将对应的值写会到之前的内存区域

而这个过程中,当执行++操作的时候,有可能其他线程已经抢先修改了i的值,当i的值写回去的时候,引发另外线程读取数据错误。为了保证这几部运算的原子性,你不得不在方法前加上synchronized关键字,使得某个时刻只能有一个线程在运行!

JDK1.5后增加了AtomicInteger类来解决并发情况提升吞吐率的问题。AtomicInteger主要提供了以下方法:

int addAndGet(int delta)

以原子方式将给定值与当前值相加。 实际上就是等于线程安全版本的i =i+delta操作。

boolean compareAndSet(int expect, int update)

如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 如果成功就返回true,否则返回false,并且不修改原值。

int decrementAndGet()

以原子方式将当前值减 1。 相当于线程安全版本的--i操作。

int get()

获取当前值。

int getAndAdd(int delta)

以原子方式将给定值与当前值相加。 相当于线程安全版本的t=i;i+=delta;return t;操作。

int getAndDecrement()

以原子方式将当前值减 1。 相当于线程安全版本的i--操作。

int getAndIncrement()

以原子方式将当前值加 1。 相当于线程安全版本的i++操作。

int getAndSet(int newValue)

以原子方式设置为给定值,并返回旧值。 相当于线程安全版本的t=i;i=newValue;return t;操作。

int incrementAndGet()

以原子方式将当前值加 1。 相当于线程安全版本的++i操作。

void lazySet(int newValue)

最后设置为给定值。 延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。如果还是难以理解,这里就类似于启动一个后台线程如执行修改新值的任务,原线程就不等待修改结果立即返回(这种解释其实是不正确的,但是可以这么理解)。

void set(int newValue)

设置为给定值。 直接修改原始值,也就是i=newValue操作。

boolean weakCompareAndSet(int expect, int update)

如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。JSR规范中说:以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生(也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,最后效果和compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。

示例代码:

AtomicInteger atomicInteger = new AtomicInteger();

       System.out.println(atomicInteger.incrementAndGet());

       System.out.println(atomicInteger.incrementAndGet());

       System.out.println(atomicInteger.incrementAndGet());

       System.out.println(atomicInteger.compareAndSet(3, 4));

       System.out.println(atomicInteger.decrementAndGet());

AtomicInteger和AtomicLong、AtomicBoolean、AtomicReference都差不多,我在这里就不一一介绍了。下面我们来看一下数组原子类的操作!

类似的,AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray的API都差不多,我们选择一个有代表性的AtomicIntegerArray来描述这些问题。

int get(int i)

获取位置 i 的当前值。很显然,由于这个是数组操作,就有索引越界的问题(IndexOutOfBoundsException异常)。

对于下面的API起始和AtomicInteger是类似的,这种通过方法、参数的名称就能够得到函数意义的写法是非常值得称赞的。在《重构:改善既有代码的设计》和《代码整洁之道》中都非常推崇这种做法。

void set(int i, int newValue)

void lazySet(int i, int newValue)

int getAndSet(int i, int newValue)

boolean compareAndSet(int i, int expect, int update)

boolean weakCompareAndSet(int i, int expect, int update)

int getAndIncrement(int i)

int getAndDecrement(int i)

int getAndAdd(int i, int delta)

int incrementAndGet(int i)

int decrementAndGet(int i)

int addAndGet(int i, int delta)

整体来说,数组的原子操作在理解上还是相对比较容易的,这些API就是有多使用才能体会到它们的好处,而不仅仅是停留在理论阶段。具体的细节和示例我就不一一列举了!

下面我们来看下,如何原子操作类中的属性。JDK提供了基于反射的类AtomicIntegerFieldUpdater<T>、AtomicLongFieldUpdater<T>、AtomicReferenceFieldUpdater<T,V>来修改对应的数据。但是使用以上几个类必须满足以下条件:

1、  字段必须是volatile类型的!

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

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

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

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

我们来看下对应的一个示例

package com.test;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater; public class AtomicClass { public volatile int x;
volatile long y;
protected volatile int z;
private volatile int n; public static void main(String[] args) {
AtomicClass demo = new AtomicClass();
System.out.println(AtomicIntegerFieldUpdater.newUpdater(AtomicClass.class, "x").addAndGet(demo, 12));
System.out.println(demo.x);
System.out.println(AtomicLongFieldUpdater.newUpdater(AtomicClass.class, "y").incrementAndGet(demo));
System.out.println(demo.y);
System.out.println(AtomicIntegerFieldUpdater.newUpdater(AtomicClass.class, "z").compareAndSet(demo, 0, 100));
System.out.println(demo.z);
System.out.println(AtomicIntegerFieldUpdater.newUpdater(AtomicClass.class, "n").decrementAndGet(demo));
System.out.println(demo.n);
}
}
package com.test; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater; public class AtomicClass { public volatile int x;
volatile long y;
protected volatile int z;
private volatile int n; public static void main(String[] args) {
AtomicClass demo = new AtomicClass();
System.out.println(AtomicIntegerFieldUpdater.newUpdater(AtomicClass.class, "x").addAndGet(demo, 12));
System.out.println(demo.x);
System.out.println(AtomicLongFieldUpdater.newUpdater(AtomicClass.class, "y").incrementAndGet(demo));
System.out.println(demo.y);
System.out.println(AtomicIntegerFieldUpdater.newUpdater(AtomicClass.class, "z").compareAndSet(demo, 0, 100));
System.out.println(demo.z);
System.out.println(AtomicIntegerFieldUpdater.newUpdater(AtomicClass.class, "n").decrementAndGet(demo));
System.out.println(demo.n);
}
}

相同的代码,因为上一个示例中main处于AtomicClass内部,而下面是外部另外的一个类,所以第一个所以的资源都可以访问,而第二个类最好一个资源n将不能正常访问。更不能修改对应的数据。

Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The field AtomicClass.n is not visible at com.test.XXClass.main(XXClass.java:18)

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

AtomicStampedReference类维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。对比AtomicMarkableReference类的<Object,Boolean>,AtomicStampedReference维护的是一种类似<Object,int>的数据结构,其实就是对对象(引用)的一个并发计数。但是与AtomicInteger不同的是,此数据结构可以携带一个对象引用(Object),且能够对此对象和计数同时进行原子操作。在后面的章节中会提到“ABA问题”,而AtomicMarkableReference/AtomicStampedReference在解决“ABA问题”上很有用。

深入浅出Java并发包—原子类操作的更多相关文章

  1. 深入浅出Java并发包—CAS机制

    在JDK1.5之前.Java主要靠synchronized这个关键字保证同步,已解决多线程下的线程不安全问题,但是这会导致锁的发生,会引发一些个性能问题. 锁主要存在一下问题 (1)在多线程竞争下,加 ...

  2. Java多线程系列——原子类的实现(CAS算法)

    1.什么是CAS? CAS:Compare and Swap,即比较再交换. jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronou ...

  3. 深入浅出Java并发包—锁机制(三)

    接上文<深入浅出Java并发包—锁机制(二)>  由锁衍生的下一个对象是条件变量,这个对象的存在很大程度上是为了解决Object.wait/notify/notifyAll难以使用的问题. ...

  4. 深入浅出Java并发包—锁机制(二)

    接上文<深入浅出Java并发包—锁机制(一)  >  2.Sync.FairSync.TryAcquire(公平锁) 我们直接来看代码 protected final boolean tr ...

  5. JUC——原子类操作(三)

    原子类操作 既然强调了并发访问,那么就必须考虑操作系统位数:32位操作系统还是64位操作系统,对于long型数据类型而言,是64位的.但是如果现在项目运行在32位系统上,则long型数据会占用32位空 ...

  6. JDK原子类操作

    JDK原子类操作及原理 在JDK5之后,JDK提供了对变量的原子类操作, java.util.concurrent.atomic里都是原子类 原子类的分类 原子更新基本类型 原子更新数组 原子更新抽象 ...

  7. Java线程--Atomic原子类使用

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11871241.html Java线程--Atomic原子类使用 package concurr ...

  8. 深入浅出Java并发包—指令重排序

    前面大致提到了JDK中的一些个原子类,也提到原子类是并发的基础,更提到所谓的线程安全,其实这些类或者并发包中的这么一些类,都是为了保证系统在运行时是线程安全的,那到底怎么样才算是线程安全呢? Java ...

  9. Java多线程—JUC原子类

    根据修改的数据类型,可以将JUC包中的原子操作类可以分为4类. 1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;2. 数组类型: AtomicIn ...

随机推荐

  1. win7 php5.5 apache 源码安装 imagick扩展

    最近公司项目有用到php 的imagick,折腾了好长时间才把扩展装上,最主要的就是最新的不一定是最合适的,最开始一直找最新包安装,一直都不成功,经过google了好长时间,终于找到一个有用的,灵机一 ...

  2. AJAX 跨域 :Access-Control-Allow-Origin

    在一个项目上想用NodeJS,在前端的JS(http://localhost/xxx)中ajax访问后端RestAPI(http://localhost:3000/….)时(Chrome)报错: XM ...

  3. CentOS 6.4 升级 Mysq5.5l方法 和 用户远程登录数据库

    一:.在这里我们都知道 系统的yum源Mysql版本一般都是5.1 5.2的比较多 但是有些程序 必须要5.5以上的版本才能支持 这时候我们应该怎么办呢  编译安装也太慢 太费时间  那么我们就必要要 ...

  4. Zencart 国家排序及中文名称的扩展

    最终实现效果如上 具体步骤: 1. 手动或SQL修改数据表,增加2个字段 ) ) '; 2. 修改admin/countries.php文件,增加表单插入编辑功能, 共计7处,此处忽略具体代码. 3. ...

  5. LLVM language 参考手册(译)(2)

    调用约定(Calling Conventions) LLVM functions, calls and invokes 可以带有一个可选的调用约定来指明调用方式.每一对 caller/callee(调 ...

  6. 存储映射IO

    mmap 将文件映射到内存, 对这块内存的修改会自动同步到相应的文件中 void *mmap(void *addr, size_t len, int prot, int flag, int fd, o ...

  7. Jackson怎样转换这样的字符串? String jsonStr = "{dataType:'Custom',regexp:'t\\d+',msg:'输入不正确'}";

    字符串 String jsonStr = "{dataType:'Custom',regexp:'t\\d+',msg:'输入不正确'}"; 实体 package com.asia ...

  8. c++各种排序

    1.插入排序 void InsertSort(int a[], int n) { int temp, i, j; ; i < n; i++) { ]) { temp = a[i]; ; j &g ...

  9. 51nod 1257 背包问题 V3

    1257 背包问题 V3 基准时间限制:3 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 N个物品的体积为W1,W2......Wn(Wi为整数),与之相对应的价值为P1,P2.. ...

  10. BLOB或TEXT字段使用散列值和前缀索引优化提高查询速度

    1.创建表,存储引擎为myisam,对大文本字段blob使用MD5函数建立一个散列值 create table t2(id varchar(60), content blob, hash_value ...