Java中的Atomic包使用指南

引言

Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

Atomic包介绍

在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。

原子更新基本类型类

用于通过原子的方式更新基本类型,Atomic包提供了以下三个类:

  • AtomicBoolean:原子更新布尔类型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新长整型。

AtomicInteger的常用方法如下:

  • int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
  • boolean compareAndSet(int expect, int update) :如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
  • int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
  • void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。关于该方法的更多信息可以参考并发网翻译的一篇文章《AtomicLong.lazySet是如何工作的?
  • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

AtomicInteger例子代码如下:

01 import java.util.concurrent.atomic.AtomicInteger;
02  
03 public class AtomicIntegerTest {
04  
05     static AtomicInteger ai = new AtomicInteger(1);
06  
07     public static void main(String[] args) {
08         System.out.println(ai.getAndIncrement());
09         System.out.println(ai.get());
10     }
11  
12 }

输出

1
2

餐后甜点

Atomic包提供了三种基本类型的原子更新,但是Java的基本类型里还有char,float和double等。那么问题来了,如何原子的更新其他的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,让我们一起看下Unsafe的源码,发现Unsafe只提供了三种CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现其是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新double也可以用类似的思路来实现。

原子更新数组类

通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:

  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。

AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下

  • int addAndGet(int i, int delta):以原子方式将输入值与数组中索引i的元素相加。
  • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

实例代码如下:

01 public class AtomicIntegerArrayTest {
02  
03     static int[] value = new int[] { 12 };
04  
05     static AtomicIntegerArray ai = new AtomicIntegerArray(value);
06  
07     public static void main(String[] args) {
08         ai.getAndSet(03);
09         System.out.println(ai.get(0));
10                 System.out.println(value[0]);
11     }
12  
13 }

输出

3
1

AtomicIntegerArray类需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。

原子更新引用类型

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类:

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

AtomicReference的使用例子代码如下:

01 public class AtomicReferenceTest {
02  
03     public static AtomicReference<user> atomicUserRef = new AtomicReference</user><user>();
04  
05     public static void main(String[] args) {
06         User user = new User("conan"15);
07         atomicUserRef.set(user);
08         User updateUser = new User("Shinichi"17);
09         atomicUserRef.compareAndSet(user, updateUser);
10         System.out.println(atomicUserRef.get().getName());
11         System.out.println(atomicUserRef.get().getOld());
12     }
13  
14     static class User {
15         private String name;
16         private int old;
17  
18         public User(String name, int old) {
19             this.name = name;
20             this.old = old;
21         }
22  
23         public String getName() {
24             return name;
25         }
26  
27         public int getOld() {
28             return old;
29         }
30     }
31 }

输出

Shinichi
17

原子更新字段类

如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。

原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。AtomicIntegerFieldUpdater的例子代码如下:

01 public class AtomicIntegerFieldUpdaterTest {
02  
03     private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater
04             .newUpdater(User.class"old");
05  
06     public static void main(String[] args) {
07         User conan = new User("conan"10);
08         System.out.println(a.getAndIncrement(conan));
09         System.out.println(a.get(conan));
10     }
11  
12     public static class User {
13         private String name;
14         public volatile int old;
15  
16         public User(String name, int old) {
17             this.name = name;
18             this.old = old;
19         }
20  
21         public String getName() {
22             return name;
23         }
24  
25         public int getOld() {
26             return old;
27         }
28     }
29 }

输出

10
11

参考资料

时间: 2024-11-05 20:47:05

Java中的Atomic包使用指南的相关文章

请问java中类库跟包的区别

问题描述 请问java中类库跟包的区别 请问类库跟包的区别是什么? 解决方案 类库包含很多个包 解决方案二: 库通常是一个组功能的集合,像Spring就是一个类库,可以理解为一个工程:而包是类的容器,用于分隔类名空间,是一个工程下的一部分. 解决方案三: 你只需要知道类库包含包就可以了 解决方案四: 类库字面意思就是类的集合,类库不等于框架,Framework 是比类库更高一级的概念.类库是一个综合性的面向对象的可重用类型集合,这些类型包括:接口.抽象类和具体类.类库可以单独从第三方软件商获得.

源码-请教关于java中jxl jar包原理

问题描述 请教关于java中jxl jar包原理 最近使用jxl操作excel,对其中源码有些疑问,表示看不懂,请问哪位大神指导源码中的Java代码是怎么操作Excel的? 解决方案 用pio吧,jxl不支持2010+的office文件 解决方案二: 楼主看看这些java书籍对你有没有用 http://download.csdn.net/album/detail/1191

jar包问题-Java中的Jar包问题。。。

问题描述 Java中的Jar包问题... 如果用Java写的代码不是那种图形界面导出后就点不开....是不是哪里错了 解决方案 你说的是jar包用批处理的方式运行吗?

Java中的static的使用指南_java

一.Java中的static使用之静态变量 1.Java 中被static修饰的成员称为静态成员或类成员.它属于整个类所有,而不是某个对象所有,即被类的所有对象所共享.且优先于对象存在.静态成员可以使用类名直接访问,也可以使用对象名进行访问.使用 static 可以修饰变量.方法和代码块. 2.public 修饰符表示公开的.公有的,静态变量使用static修饰 3.静态方法中可以直接调用同类中的静态成员,但不能直接调用非静态成员. public class HellWorld{ String

java.util.concurrent包源码阅读(二) java.util.concurrent.atomic包

Aomic数据类型有四种类型:AomicBoolean, AomicInteger, AomicLong, 和AomicReferrence(针对Object的)以及它们的数组类型, 还有一个特殊的AomicStampedReferrence,它不是AomicReferrence的子类,而是利用AomicReferrence实现的一个储存引用和Integer组的扩展类 首先,所有原子操作都是依赖于sun.misc.Unsafe这个类,这个类底层是由C++实现的,利用指针来实现数据操作 关于CAS

Java中的包和文档

什么是包 Java中的包(Package)其实指的就是目录,它是为了更好地管理Java类(Class)和接口(Interface).Java语言的包可以被另一个Java开发包所使用.如果我们要引用某个包中的类,用import关键字来标明即可.比如: import java.util. date=new Date 提示:import java.util.表示java.util中的所有公有类和接口被引入到当前包.这里的匹配符可以调入多个类名. 常用的Java标准包 JDK为我们提供了很多标准的Jav

深入解读Java代码组织中的package包结构_java

如果我们在Class对象上调用getPackage方法,就可以得到描述该类所在包的Package对象(Package类是在java.lang中定义的).我们也可以用包名通过调用静态方法getPackage或者调用静态方法getPackages(该方法返回由系统中所有已知包构成的数组)来获得Package对象.getName方法可以返回包的全名. Package对象的使用与其他反射类型完全不同,即我们不能在运行时创建或操纵包.我们可以使用Package对象来获取有关包的信息,诸如包的用途.谁创建了

Java 8 新特性终极版指南详解_java

前言: Java 8已经公布有一段时间了,种种迹象表明Java 8是一个有重大改变的发行版.在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java 8 – Lambdas and Concurrency.Java 8 Date Time API Tutorial : LocalDateTime和Abstract Class Versus Interface in the JDK 8 Era.本文还参考了一些其他资料,例如:15 Must

Java中无锁HashMap的原理与实现教程

在<Java出现HashMap的死循环的原因及解决方法>中,我们看到,java.util.HashMap并不能直接应用于多线程环境.对于多线程环境中应用HashMap,主要有以下几种选择: 使用线程安全的java.util.Hashtable作为替代. 使用java.util.Collections.synchronizedMap方法,将已有的HashMap对象包装为线程安全的. 使用java.util.concurrent.ConcurrentHashMap类作为替代,它具有非常好的性能.