我们知道,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. c#获取今天星期几

    System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetDayName(DateTime.Now.DayOfWeek)

  2. mysql在windows下支持表名大小写,lower_case_table_names

    windows下mysql默认是不支表名大小写的,也就是表名大小写不敏感.用phpmyadmin创建的驼峰式表名,全部被强制成小写.mysql表名大小写敏感的参数: lower_case_table_ ...

  3. [转]init.d解析

    本文为转载,放在这里以备忘. init.d指的是/etc/rc.d/init.d目录.本文包括3部分内容1. Linux的引导过程2. 运行级别3. /etc/rc.d/ 与/etc/rc.d/ini ...

  4. uWSGI uwsgi_response_write_body_do(): Connection reset by peer 报错的解决方法

    服务器架构是:Nginx+uWSGI+Django 某一天,发现服务器返回的response不完整,例如文档大小是200K的,但是只返回了100K给浏览器. 查了一下uWSGI的日志,发现以下错误: ...

  5. 每日一“酷”之Cookie

    Cookie---Http Cookie 作用:Cookie模块定义一些类来解析和创建HTTP cookie首部 Cookie模块为大多数符合RFC 2109的cookie实现一个解析器.这个实现没有 ...

  6. WPF:实现主应用程序单一实例运行方式总结

       本文介绍常见的实现主应用程序单一实例运行的几种方式. 方式一: public partial class App : Application { protected override void ...

  7. hdu 5738 2016 Multi-University Training Contest 2 Eureka 计数问题(组合数学+STL)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5738 题意:从n(n <= 1000)个点(有重点)中选出m(m > 1)个点(选出的点只 ...

  8. 万网域名解析到IP地址

    进入https://home.console.aliyun.com/#/的阿里云控制台 再自己购买的域名列表里进行操作 添加一个A解析

  9. SQLserver利用系统时间生成“2015-11-30 00:00:00.000”类型的时间

    select getdate() ---当前时间:2015-12-18 10:20:24.097 -------------------建立测试表 Create Table #Test ( ID IN ...

  10. MySQL通过Binlog恢复删除的表

    查看log-bin是否开启:mysql> show variables like '%log%bin%';+---------------------------------+-------+| ...