JUC中原子类的操作
文章目录
- JUC中原子类的操作
- 前言
- 一、简图
- 二、基本类型原子类
- 三、数组类型的原子类
- 四、引用类型的原子操作类
- 五、对象属性修改的原子类操作
前言
atomic译为原子,原子在化学中中表示物质最小的单位是不可分割。而在多线程中原子类(具有原子操作特性的类)中的操作是不可中断的。即时在多线程的情况下原子操作一旦开始不会受到其它线程的干扰。
一、简图
atomic中原子操作类简图
atomic原子操作得益于:JMM volatile UnSafe CAS机制
二、基本类型原子类
AtomicInteger
AtomicLong
AtomicBoolean
常用方法:
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 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
举例:自增
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
this表示当前对象
valueOffset表示字段的偏移量
1:表示自增的值
//我们通过this+valueOffset来获取需要操作的字段位置
源码:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
--------------------
getIntVolatile(var1,var2);确保获取的值是最新的
compareAndSwapInt()-->使用CAS机制:比较和交换,
如果内存值与我们的预期值相等的话就返回true,并将最新的结果返回。
如果不相等就返回false,继续此时的do while循环直至成功为止。
案例:
来100个线程每个线程将变量值自增100.
使用两种方法比较时间:
案例一:使用原子操作类:
import sun.misc.Unsafe;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class HahaTest {
//定义一个AtomicInteger对象
static AtomicInteger count = new AtomicInteger();
public static void main(String[] args) {
Long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try{
for (int i1 = 0; i1 < 100; i1++) {
increase();
}
}finally {
countDownLatch.countDown();
}
}
}).start();
}
try {
countDownLatch.await();
Long end = System.currentTimeMillis();
System.out.println("总耗时"+(end-start)+"毫秒,最终的结果为"+count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void increase(){
count.getAndIncrement();
}
}
结果:
案例二:使用synchronized同步锁:
import sun.misc.Unsafe;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class HahaTest {
//定义一个AtomicInteger对象
static int count = 0;
public static void main(String[] args) {
Long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(100);
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try{
for (int i1 = 0; i1 < 100; i1++) {
increase();
}
}finally {
countDownLatch.countDown();
}
}
}).start();
}
try {
countDownLatch.await();
Long end = System.currentTimeMillis();
System.out.println("总耗时"+(end-start)+"毫秒,最终的结果为"+count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static synchronized void increase(){
count++;
}
}
结果:
两个结果都是正确的,但是时间上使用原子类的相对较短。此次我们在自增的时候没有休眠,如果我们让程序休眠一会那么两者的差距就会变得十分明显。
三、数组类型的原子类
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
常用方法:
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,int j) //获取 index=dalta 位置元素的值,并加上预期的值j
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
案例:
模拟一个学校有5间教室,一共来个5批学生每批学生都有100人。看最后每个教室的人数
import sun.misc.Unsafe;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class HahaTest {
//定义一个AtomicInteger对象
static AtomicIntegerArray room = new AtomicIntegerArray(5);
public static void main(String[] args) {
Long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(5);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
for (int i1 = 0; i1 < 100; i1++) {
increase(i);
}
countDownLatch.countDown();
}
}
});
thread.start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 5; i++) {
System.out.println("第"+(i+1)+"间教室一共有"+room.get(1)+"名学生");
}
}
public static void increase(int i){
room.getAndIncrement(i);
}
}
结果:
该处使用的url网络请求的数据。
四、引用类型的原子操作类
AtomicReference :引用类型原子类 AtomicStampedRerence :原子更新引用类型里的字段原子类 AtomicMarkableReference :原子更新带有标记位的引用类型
AtomicReference是对象引用的封装,可以保证对象引用在修改的时候线程安全
常用方法:
boolean compareAndSet(V expect, V update)
如果当前值 ==为预期值,则将值设置为给定的更新值。
V get()
获取当前值。
getAndSet(V newValue)
将原子设置为给定值并返回旧值。
V getAndUpdate(UnaryOperator<V> updateFunction)
用应用给定函数的结果原子更新当前值,返回上一个值。
void lazySet(V newValue)
最终设定为给定值。
void set(V newValue)
设置为给定值。
模拟案例:
一家商场为了吸引客户,凡是新用户的话就将赠送20元的优惠卷,每个人只能赠送一次。
代码模拟消费和充值的过程:
import java.util.concurrent.atomic.AtomicReference;
public class ShopTest {
static int nowMoney = 19;
static AtomicReference<Integer> atomic = new AtomicReference<>(nowMoney);
public static void main(String[] args) {
chong();
xiao();
}
//模拟两个线程同时充值赠送优惠卷
static void chong(){
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//首先获取当前的money
int money = atomic.get();
if( atomic.compareAndSet(money,money+20)){
System.out.println("为该用户赠送了20元优惠卷,用户余额"+atomic.get());
}
try {
//模拟休眠,让另一个线程也去充值
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
static void xiao(){
for (int i = 0; i < 5; i++) {
//获取当前的余额
int money = atomic.get();
if(money > 5){
if(atomic.compareAndSet(money,money-5)){
System.out.println("该用户消费了5元,此时余额"+atomic.get()+"元");
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
上述问题其实就是我们说的ABA问题。我们知道在出现ABA问题的时候。我们可以使用版本号来解决。
ABA问题是CAS机制的一个最大弊端。
CAS在使用时会先比较内存值V和预期值是否相等,若相等则将新值B保存。否则循环直至成功返回true
ABA问题就是一个线程在比较时,另一个线程修改了值后又修改了一次。
导致第一个线程在判断时比较为true,其实该值已经变化了
A---B----A ===>该值其实变化了,但在CAS机制看来是一致的。
atomic包中AtomicStampedRerence也可以解决ABA问题
AtomicStampedReference在使用的时候除了要传递预期值和更新值外
还需要传递期望时间戳和新的时间戳,只有期望时间戳和新的时间戳一致的话
才进行新值的保存。
当数值更改的话,除了修改数值外还需要修改时间戳。
//比较设置,参数依次为:期望值、写入新值、期望时间戳、新时间戳
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp);
//获得当前对象引用
public V getReference();
//获得当前时间戳
public int getStamp();
//设置当前对象引用和时间戳
public void set(V newReference, int newStamp);
上述代码修改:
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ShopTest {
static int nowMoney = 19;
static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(nowMoney,0);
public static void main(String[] args) {
chong();
xiao();
}
//模拟两个线程同时充值赠送优惠卷
static void chong(){
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//首先获取当前的money
int money = atomic.getReference();
int temp = atomic.getStamp();
if( atomic.compareAndSet(money,money+20,temp,temp+1)){
System.out.println("为该用户赠送了20元优惠卷,用户余额"+atomic.getReference());
}
try {
//模拟休眠,让另一个线程也去充值
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
static void xiao(){
for (int i = 0; i < 5; i++) {
//获取当前的余额
int money = atomic.getReference();
int temp = atomic.getStamp();
if(money > 5){
if(atomic.compareAndSet(money,money-5,temp,temp+1)){
System.out.println("该用户消费了5元,此时余额"+atomic.getReference()+"元");
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
五、对象属性修改的原子类操作
AtomicIntegerFieldUpdater:原子更新整形字段的值 AtomicLongFieldUpdater:原子更新长整形字段的值 AtomicReferenceFieldUpdater :原子更新应用类型字段的值
修改对象属性的步骤:
第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
第二步,更新的对象属性必须使用 public volatile 修饰符。
举例:AtomicReferenceFieldUpdater的使用
public static <U, W> AtomicReferenceFieldUpdater<U, W> newUpdater(
Class<U> tclass,
Class<W> vclass,
String fieldName)
tclass:字段所属的类
vclass:字段类型
fieldName:字段名称
案例:
只有字段为false时才进行修改
import com.sun.org.apache.xpath.internal.operations.Bool;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class Demo {
static Demo demo = new Demo();
volatile Boolean aBoolean = Boolean.FALSE;
AtomicReferenceFieldUpdater<Demo,Boolean> atomic = AtomicReferenceFieldUpdater.newUpdater(Demo.class, Boolean.class,"aBoolean");
public synchronized void init(){
if(atomic.get(demo)){
System.out.println("该字段的值为true不需要修改");
}
if(atomic.compareAndSet(demo,false,true)){
System.out.println("修改后的字段值为"+atomic.get(demo));
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
demo.init();
}
}).start();
}
}
}
结果:
上述代码中第一次调用init方法的时候,CAS判断时,内存中的值是false,和预期值一致,因此做了修改
当别的线程调用init方法时,发现与预期值不一致,则放弃修改了。