(一)原子类简介
当程序更新一个
变
量
时
,如果多
线
程同
时
更新
这
个
变
量,可能得到期望之外的
值
,比如
变量
i=1
,
A
线
程更新
i+1
,
B
线
程也更新
i+1
,
经过
两个
线
程操作之后可能
i
不等于
3
,而是等于
2
。因
为
A
和
B
线
程在更新
变
量
i
的
时
候拿到的
i
都是
1
,
这
就是
线
程不安全的更新操作,通常我
们
会使
用
synchronized
来解决
这
个
问题
,
synchronized
会保
证
多
线
程不会同
时
更新
变
量
i
。
而
Java
从
JDK 1.5
开始提供了
java.util.concurrent.atomic
包(以下
简
称
Atomic
包),
这
个包中
的原子操作
类
提供了一种用法
简单
、性能高效、
线
程安全地更新一个
变
量的方式。
因
为变
量的
类
型有很多种,所以在
Atomic
包里一共提供了
13
个
类
,属于
4
种
类
型的原子更
新方式,分
别
是原子更新基本
类
型、原子更新数
组
、原子更新引用和原子更新属性(字段)。
Atomic
包里的
类
基本都是使用
Unsafe
实 现
的包装
类
。
(二)原子更新基本类型类
1.atomic包下的所有类示意图
2.使用原子的方式更新基本
类
型,
Atomic
包提供了以下
3
个
类。
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新长整型
以上
3
个
类
提供的方法几乎一模一
样
,所以
以
AtomicInteger
为
例
进
行
讲
解、
AtomicInteger
的常用方法如下:
- int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的 value)相加,并返回结果。
- boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
- int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值,等价于i++。
- int incrementAndGet():以原子方式将当前值加1,等价于++1。
3.AtomicBoolean案例演示 参考:AtomicBoolean介绍与使用
@Slf4j
@ThreadSafe
public class AtomicBooleanTest {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
test();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappened:{}", isHappened.get());
}
private static void test() {
//并发同步控制,保证只有一个线程执行
if (isHappened.compareAndSet(false, true)) {
log.info("execute");
}
}
}
4.AtomicInteger案例演示
//结果:i = 10 j = 20
AtomicInteger atomicInteger = new AtomicInteger();
int i = atomicInteger.addAndGet(10);
int j = atomicInteger.addAndGet(10);
System.out.println("i = " + i);
System.out.println("j = " + j);
//等价于i++ reuslt1 = 0
AtomicInteger atomicInteger1 = new AtomicInteger();
int reuslt1 = atomicInteger1.getAndIncrement();
System.out.println("reuslt1 = " + reuslt1);
//等价于++i reuslt2 = 1
AtomicInteger atomicInteger2 = new AtomicInteger();
int reuslt2 = atomicInteger2.incrementAndGet();
System.out.println("reuslt2 = " + reuslt2);
//result3 = 100
AtomicInteger atomicInteger3 = new AtomicInteger(10);
atomicInteger3.getAndSet(100);
int result3 = atomicInteger3.get();
System.out.println("result3 = " + result3);
//CAS乐观锁和数据库乐观锁类似 b1 = false b2 = true
AtomicInteger atomicInteger4 = new AtomicInteger(10);
boolean b1 = atomicInteger4.compareAndSet(11, 100);
boolean b2 = atomicInteger4.compareAndSet(10, 100);
System.out.println("b1 = " + b1);
System.out.println("b2 = " + b2);
5.使用原子类确保线程安全
@NotThreadSafe
public class UnsafeSequences {
//多个线程并发访问共享变量n,线程不安全
private int n;
private int add(){
return n++;
}
public static void main(String[] args) {
Thread t ;
UnsafeSequences unsafeSequences =new UnsafeSequences();
for (int i = 0; i <10000 ; i++) {
t= new Thread(new Runnable() {
@Override
public void run() {
int reuslt = unsafeSequences.add();
System.out.println("reuslt = " + reuslt);
}
});
t.start();
}
}
}
@ThreadSafe
public class SafeSequence {
private static AtomicInteger atomicInteger =new AtomicInteger();
private int add(){
return atomicInteger.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
Thread t ;
for (int i = 0; i <10000 ; i++) {
t= new Thread(new Runnable() {
@Override
public void run() {
new SafeSequence().add();
}
});
t.start();
t.join();
}
System.out.println(atomicInteger.get());
}
}
6.getAndIncrement方法实现原子操作原理分析
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
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;
}
var1:指的是AtomicInteger对象
var2:指的是valueOffset内存首地址偏移量--当前值
var4:指的是常量1
var5:内存中的值--期望值
当var2当前值和var5期望值相等的情况下,将值更新为var5+var4
(三)原子更新数组
1.通过原子的方式更新数组里的某个元素,Atomic包提供了以下4个类。
- AtomicIntegerArray:原子更新整型数组里的元素
- AtomicLongArray:原子更新长整型数组里的元素
- AtomicReferenceArray:原子更新引用类型数组里的元素
AtomicIntegerArray
类
主要是提供原子的方式更新数
组
里的整型,其常用方法如下:
- ·int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加
- boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子
方式将数 组 位置 i 的元素 设 置成 update 值 。
2.案例演示
@ThreadSafe
public class AtomicIntegerArrayDemo {
public static void main(String[] args) {
int[] a = {5,6,7,8,9};
AtomicIntegerArray array = new AtomicIntegerArray(a);
array.addAndGet(0,1);
int result = array.get(0);
//result = 6
System.out.println("result = " + result);
//array = [6, 6, 7, 8, 9]
System.out.println("array = "+array.toString());
//array = [5, 6, 7, 8, 9]
array.compareAndSet(0,6,5);
System.out.println("array = "+array.toString());
}
}
(四) 原子更新引用类型
原子更新基本
类
型的
AtomicInteger
,只能更新一个
变
量,如果要原子更新多个
变
量,就需
要使用
这
个原子更新引用
类
型提供的
类
。
Atomic
包提供了以下
3
个
类
:
- AtomicReference:原子更新引用类型
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类 型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef,boolean initialMark)
1.AtomicReference案例演示
public class AtomicReferenceTest {
private static AtomicReference atomicReference = new AtomicReference();
public static void main(String[] args) {
Person person1 = new Person();
person1.setName("zhangsan");
person1.setAge(18);
atomicReference.set(person1);
Person person2 = new Person();
person2.setName("lisi");
person2.setAge(22);
atomicReference.compareAndSet(person1,person2);
//结果:Person{name='lisi', age=22}
System.out.println(atomicReference);
}
static class Person{
private String name;
private int age;
}
}
2.AtomicReferenceFieldUpdater案例演示
public class AtomicReferenceFieldUpdaterTest {
private static AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Person.class,String.class,"name");
public static void main(String[] args) {
Person person = new Person();
person.setName("zhangsan");
person.setAge(18);
updater.compareAndSet(person, person.getName(),"lisi");
//结果:name = lisi
System.out.println("name = "+person.getName());
//将lisi修改为wangwu
updater.getAndSet(person,"wangwu");
System.out.println("name = "+person.getName());
}
static class Person{
volatile String name;
volatile int age;
}
}
(五)原子更新类字段
如果需原子地更新某个
类
里的某个字段
时
,就需要使用原子更新字段
类
,
Atomic
包提供了以下
3
个
类进
行原子字段更新。
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
- AtomicLongFieldUpdater:原子更新长整型字段的更新器
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起
来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进 行原子更新 时 可能出 现 的ABA 问题 。
要想原子地更新字段
类
需要两步:
第一步,因
为
原子更新字段
类
都是抽象
类
,每次使用的时
候必
须
使用静
态
方法
newUpdater()
创
建一个更新器,并且需要
设
置想要更新的
类
和属性。
第
二步,更新
类
的字段(属性)必
须
使用
public volatile
修
饰
符。
AtomicIntegerFieldUpdater
案例演示
@Slf4j
public class AtomicIntegerFieldUpdaterTest {
//第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
private static AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterTest> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdaterTest.class, "count");
//第二步,更新类的字段(属性)必须使用public volatile修饰符。
@Getter
public volatile int count = 100;
public static void main(String[] args) {
AtomicIntegerFieldUpdaterTest updaterTest = new AtomicIntegerFieldUpdaterTest();
if (updater.compareAndSet(updaterTest, 100, 120)) {
//update success 1, 120
log.info("update success 1, {}", updaterTest.getCount());
}
if (updater.compareAndSet(updaterTest, 100, 120)) {
log.info("update success 2, {}", updaterTest.getCount());
} else {
// update failed 3, 120
log.info("update failed 3, {}", updaterTest.getCount());
}
}
}
(六)Atomic包下相关面试题目