Java多线程学习(九)JUC 中的 Atomic 原子类总结

47次阅读

共计 6633 个字符,预计需要花费 17 分钟才能阅读完成。

个人觉得这一节掌握基本的使用即可!
本节思维导图:

1 Atomic 原子类介绍
Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以,所谓原子类说简单点就是具有原子 / 原子操作特征的类。
并发包 java.util.concurrent 的原子类都存放在 java.util.concurrent.atomic 下, 如下图所示。

根据操作的数据类型,可以将 JUC 包中的原子类分为 4 类
基本类型
使用原子的方式更新基本类型

AtomicInteger:整形原子类
AtomicLong:长整型原子类
AtomicBoolean:布尔型原子类

数组类型
使用原子的方式更新数组里的某个元素

AtomicIntegerArray:整形数组原子类
AtomicLongArray:长整形数组原子类
AtomicReferenceArray:引用类型数组原子类

引用类型

AtomicReference:引用类型原子类
AtomicStampedRerence:原子更新引用类型里的字段原子类
AtomicMarkableReference:原子更新带有标记位的引用类型

对象的属性修改类型

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

下面我们来详细介绍一下这些原子类。
2 基本类型原子类
2.1 基本类型原子类介绍
使用原子的方式更新基本类型

AtomicInteger:整形原子类
AtomicLong:长整型原子类
AtomicBoolean:布尔型原子类

上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。
AtomicInteger 类常用方法
public final int get() // 获取当前的值
public final int getAndSet(int newValue)// 获取当前的值,并设置新的值
public final int getAndIncrement()// 获取当前的值,并自增
public final int getAndDecrement() // 获取当前的值,并自减
public final int getAndAdd(int delta) // 获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) // 如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)// 最终设置为 newValue, 使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
2.2 AtomicInteger 常见方法使用
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

public static void main(String[] args) {
// TODO Auto-generated method stub
int temvalue = 0;
AtomicInteger i = new AtomicInteger(0);
temvalue = i.getAndSet(3);
System.out.println(“temvalue:” + temvalue + “; i:” + i);//temvalue:0; i:3
temvalue = i.getAndIncrement();
System.out.println(“temvalue:” + temvalue + “; i:” + i);//temvalue:3; i:4
temvalue = i.getAndAdd(5);
System.out.println(“temvalue:” + temvalue + “; i:” + i);//temvalue:4; i:9
}

}
2.3 基本数据类型原子类的优势
通过一个简单例子带大家看一下基本数据类型原子类的优势
①多线程环境不使用原子类保证线程安全(基本数据类型)
class Test {
private volatile int count = 0;
// 若要线程安全执行执行 count++,需要加锁
public synchronized void increment() {
count++;
}

public int getCount() {
return count;
}
}
②多线程环境使用原子类保证线程安全(基本数据类型)
class Test2 {
private AtomicInteger count = new AtomicInteger();

public void increment() {
count.incrementAndGet();
}
// 使用 AtomicInteger 之后,不需要加锁,也可以实现线程安全。
public int getCount() {
return count.get();
}
}

2.4 AtomicInteger 线程安全原理简单分析
AtomicInteger 类的部分源码:
// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField(“value”));
} catch (Exception ex) {throw new Error(ex); }
}

private volatile int value;
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
CAS 的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个 volatile 变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
3 数组类型原子类
3.1 数组类型原子类介绍
使用原子的方式更新数组里的某个元素

AtomicIntegerArray:整形数组原子类
AtomicLongArray:长整形数组原子类
AtomicReferenceArray:引用类型数组原子类

上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。
AtomicIntegerArray 类常用方法
public final int get(int i) // 获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)// 返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)// 获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) // 获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int delta) // 获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int expect, int update) // 如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)// 最终 将 index=i 位置的元素设置为 newValue, 使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
3.2 AtomicIntegerArray 常见方法使用

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayTest {

public static void main(String[] args) {
// TODO Auto-generated method stub
int temvalue = 0;
int[] nums = { 1, 2, 3, 4, 5, 6};
AtomicIntegerArray i = new AtomicIntegerArray(nums);
for (int j = 0; j < nums.length; j++) {
System.out.println(i.get(j));
}
temvalue = i.getAndSet(0, 2);
System.out.println(“temvalue:” + temvalue + “; i:” + i);
temvalue = i.getAndIncrement(0);
System.out.println(“temvalue:” + temvalue + “; i:” + i);
temvalue = i.getAndAdd(0, 5);
System.out.println(“temvalue:” + temvalue + “; i:” + i);
}

}
4 引用类型原子类
4.1 引用类型原子类介绍
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。

AtomicReference:引用类型原子类
AtomicStampedRerence:原子更新引用类型里的字段原子类
AtomicMarkableReference:原子更新带有标记位的引用类型

上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。
4.2 AtomicReference 类使用示例
import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceTest {

public static void main(String[] args) {
AtomicReference<Person> ar = new AtomicReference<Person>();
Person person = new Person(“SnailClimb”, 22);
ar.set(person);
Person updatePerson = new Person(“Daisy”, 20);
ar.compareAndSet(person, updatePerson);

System.out.println(ar.get().getName());
System.out.println(ar.get().getAge());
}
}

class Person {
private String name;
private int age;

public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

}
上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下:
Daisy
20
5 对象的属性修改类型原子类
5.1 对象的属性修改类型原子类介绍
如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。

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

要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater() 创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerFieldUpdater 为例子来介绍。
5.2 AtomicIntegerFieldUpdater 类使用示例
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, “age”);

User user = new User(“Java”, 22);
System.out.println(a.getAndIncrement(user));// 22
System.out.println(a.get(user));// 23
}
}

class User {
private String name;
public volatile int age;

public User(String name, int age) {
super();
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

}
输出结果:
22
23
【强烈推荐】最后,给大家推荐一下阿里云最新双 11 福利活动(仅限阿里云新用户购买,老用户拉新用户可以获得返现红包,后续有机会平分百万红包),优惠力度非常非常非常大,另外加入拼团,后续还有机会平分 100w 红包!目前我的战队已经有 50 多位新人了,现在是折上 5 折了也就是 1 折购买,已经达到了最低折扣!!!!!!。划重点了:1 核 2G 云服务器 1 年仅需 99.5 元!!!1 核 2G 云服务器 3 年仅需 298.50 元!!!一个月仅需 8.2 元 该折扣仅限新人!这是我的团队拼团地址:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCciiM.hf47liqn!
另外,老用户可以加入我的战队帮忙拉新,拉新你可以获得什么福利呢?①即时红包,即拆即用(最低红包 10 元,最高 1111 元);②瓜分百万红包的机会(目前我的战队已经有 29 位新人,所以冲进前 100 的可能性非常大!冲进之后即可瓜分百万红包!)③返现奖励,如果你邀请了新人你会获得返现奖励,返现奖励直接到你的账户!(我希望我的团队最后能够冲进前 100,别的不多说!!!诚信!)

正文完
 0