并发编程系列之掌握原子类使用
Posted smileNicky
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程系列之掌握原子类使用相关的知识,希望对你有一定的参考价值。
并发编程系列之掌握原子类使用
学习目标:
- 知道什么是原子类和用途
- 掌握juc中原子类使用
- 了解原子类的实现原理
1、什么是原子类?
原子类是jdk的juc包中提供的对单个变量进行无锁、线程安全修改的工具类。
juc中提供的锁,能很好地保证线程安全,但是在高并发的情况下,可能不能保证高性能,所以适当地使用原子类,有时候是可以提高性能
2、掌握原子类api
2.1、AtomicInteger/AomicLong/AtomicBoolean
主要用途,对int,long变量提供原子更新,典型的应用场景是计数、计票等
AtomicInteger
例子,使用AtomicInteger
进行统计计数
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
public class AtomicIntegerExample
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static Integer count()
return atomicInteger.getAndIncrement();
public static void main(String[] args) throws InterruptedException
int threadSize = 500;
final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
long start = System.currentTimeMillis();
for (int i = 0 ; i < threadSize ; i ++ )
new Thread(() ->
for (int n = 0 ; n < 10_000 ; n++)
count();
countDownLatch.countDown();
).start();
countDownLatch.await();
System.out.println("统计计数:" + atomicInteger.get());
统计计数:5000000
耗时:344ms
不过在前面我们学习了juc中的各种锁,如果不使用原子类,使用锁来实现,性能如何?
private static volatile int count = 0;
private static ReentrantLock reentrantLock = new ReentrantLock();
public static Integer countLock()
reentrantLock.lock();
try
count ++;
finally
reentrantLock.unlock();
return count;
统计计数:5000000
耗时:667ms
打印多几次,发现加锁的情况统计是相对比较慢的,不过也能保证统计的线程安全
如果不加锁,仅仅使用volatile关键字?运行几次,发现统计的数值是有偏差的,所以volatile是不一定能保证线程安全的
统计计数:4128826
耗时:332ms
2.2、AtomicReference
提供对引用类型变量的原子更新
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceExample
public static void main(String[] args)
User user1 = new User("tom", "***");
User user2 = new User("jack", "***");
User user3 = new User("admin", "***");
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
System.out.println(atomicReference.compareAndSet(user1 , user2));
System.out.println(atomicReference.get());
System.out.println(atomicReference.compareAndSet(user1, user3));
System.out.println(atomicReference.get());
static class User
private String username;
private String password;
User(String username , String password)
this.username = username;
this.password = password;
@Override
public String toString()
return "User" +
"username='" + username + '\\'' +
", password='" + password + '\\'' +
'';
true
Userusername='jack', password='***'
false
Userusername='jack', password='***'
2.3 AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray
提供对对应类型数组的原子更新
2.4 AtomicIntegerFieldUpdater/AtomicLongFieldUpdater/AtomicReferenceFieldUpdater
基于反射的,对应类型的属性原子更新器。使用规则:
- 字段必须是volatile类型的,在线程之间共享变量时保证可见性
- 在执行更新代码中一定要保证能直接访问到该变量,不管字段修饰符是
public/protected/private
- 对于父类的字段,子类是不能直接操作的,尽管子类可以访问到父类的字段
- 只要是实例变量,不能是类变量,也就是说不能加
static
关键字 - 对于
AtomicIntegerFieldUpdater
和AtomicLongFieldUpdater
只能修改int/long
类型的字段,不能修改其包装类型Integer/Long
,如果要修改包装类型就需要使用AtomicReferenceFieldUpdater
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicFieldUpdaterExample
static class ParentBean
volatile int count;
static class DemoBean extends ParentBean
volatile int count;
public DemoBean ()
this.count = 0;
public int getCount ()
return count;
public static void main(String[] args) throws InterruptedException
DemoBean obj = new DemoBean();
AtomicIntegerFieldUpdater<DemoBean> atomicIntegerFieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(DemoBean.class , "count");
int threadSize = 50;
CountDownLatch countDownLatch = new CountDownLatch(threadSize);
for (int t = 0 ; t < threadSize ; t++)
new Thread(()->
for (int i = 0 ;i < 10_000; i ++)
atomicIntegerFieldUpdater.incrementAndGet(obj);
countDownLatch.countDown();
).start();
countDownLatch.await();
System.out.println("统计计数:"+obj.getCount());
如果不加上volatile关键字,出现错误,验证了上面的规则,其它规则也可以一个一个验证
Exception in thread “main” java.lang.IllegalArgumentException: Must be volatile type
2.5、什么是ABA问题?
ABA问题:举个例子来说明,在并发多线程环境,有个线程t1对变量进行修改改为A,然后改为B,接着又改回A,另外一个线程t2获取变量的值,做一系列操作,比如A改为C,但是获取的变量值为A,这个值可能是第一个,t1没修改之前的值,也有可能是t1修改后第一个A值,如果业务处理不允许这样的,就会有ABA问题
例子,下面使用AtomicInteger进行模拟
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicStampedReferenceExample
// 初始值为100
private static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) throws InterruptedException
// 线程t1进行两次修改,模拟ABA问题
Thread t1 = new Thread(()->
atomicInteger.compareAndSet(100 , 101);
atomicInteger.compareAndSet(101,100);
);
// t2线程获取值,进行修改
Thread t2 = new Thread(()->
try
// 线程睡眠1s
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
boolean flag = atomicInteger.compareAndSet(100,102);
// true,说明不能识别处理ABA问题
System.out.println(flag);
);
t1.start();
t2.start();
t1.join();
t2.join();
不过在JUC的原子类里,提供了AtomicStampedReference
和AtomicMarkableReference
来处理ABA问题:
AtomicStampedReference
:加入了int类型的邮戳(版本号),记录了变更的次数,来处理ABA问题AtomicMarkableReference
:加入了一个boolean类型的标识,标识值是否变更了,来处理ABA问题
使用AtomicStampedReference
处理ABA问题
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceExample
private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,0);
public static void main(String[] args) throws InterruptedException
Thread tt1 = new Thread(()->
// 每次修改都加上邮戳值,邮戳值加1
atomicStampedReference.compareAndSet(100 , 101,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
atomicStampedReference.compareAndSet(101, 100 ,
atomicStampedReference.getStamp() , atomicStampedReference.getStamp() + 1);
);
Thread tt2 = new Thread(()->
// 获取邮戳版本号
int stamp = atomicStampedReference.getStamp();
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
// false,因为记录了变更次数邮戳值,所以可以处理ABA问题
boolean flag = atomicStampedReference.compareAndSet(100,102 ,
stamp , stamp+1);
System.out.println(flag);
);
tt1.start();
tt2.start();
以上是关于并发编程系列之掌握原子类使用的主要内容,如果未能解决你的问题,请参考以下文章