目录
AtomicInteger和AtomicIntegerFieldUpdater有什么区别?
原子类是CAS思想的应用!如果不清楚CAS可查看上一章节。
所谓原子类,前面也说过了,就是指 java.util.concurrent.atomic包下的所有的类:
学习上,都是相通的,没必要挨个去学习。
我们可以将上述的原子类进行再分类:
一、基本类型原子类
常见常用API:
代码演示
package com.hssy.sqldemo.juc;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDemo {
public static void main(String[] args) {
int threadCount = 50;
MyNumber myNumber = new MyNumber();
for (int i = 0; i < threadCount; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
myNumber.addPlus();
}
}).start();
}
// 需要等待一下,让所有线程都执行完成,否则后面获取到的值并不一定是最终值
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(myNumber.get());
}
}
class MyNumber{
private AtomicInteger atomicInteger = new AtomicInteger();
public int get() {
return atomicInteger.get();
}
public void addPlus() {
atomicInteger.getAndIncrement();
}
}
ok~
不过这里补充一点额外知识,因为我们要获取最终的值必须等到所有线程任务都执行结束。
否则,获取到的结果就不准确。那么如何判断线程任务是否执行结束呢?
最先想到的可能是像上面这样,给主线程停一段时间,预估任务执行完成后再获取值。
这样其实是不对的,一是必须等到sleep时间过后才能去获取,二是如果出现网络抖动等各种问题,是无法确定线程实际执行时间的。
那么,有什么办法解决了?
使用闭锁CountDownLatch
package com.hssy.sqldemo.juc;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class AtomicIntegerDemo {
/**
* 可以使用闭锁
*
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
int threadCount = 50;
MyNumber myNumber = new MyNumber();
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(()->{
try {
for (int j = 0; j < 1000; j++) {
myNumber.addPlus();
}
}finally {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
System.out.println(myNumber.get());
}
}
class MyNumber{
private AtomicInteger atomicInteger = new AtomicInteger();
public int get() {
return atomicInteger.get();
}
public void addPlus() {
atomicInteger.getAndIncrement();
}
}
通过实际执行,发现只要任务线程任务执行结束,就会给闭锁减少一把,当闭锁减少到0的时候,await()方法将不再阻塞,也就直接输出后续的代码了。
二、数组类型原子类
以AtomicIntegerArray为例:
三、引用类型原子类
原子引用类很重要,作用是可以自定义原子类。这在前一章已经讲过了。
我们也使用它AtomicReference写过自旋锁。
也清楚AtomicReference在进行比较并交换的时候,可能会有ABA问题。
通过AtomicStampedReference带有版本号戳记的原子引用类,可以解决ABA问题。
那么,AtomicMarkableReference这个类是干嘛的呢?
其实,它也是一个戳记原子引用类,但是它是状态戳,但是它只能使用一次,因此也叫做一次性戳。
代码演示一下
package com.hssy.sqldemo.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
/**
* 结论:
* AtomicStampedReference 的戳是 版本号戳
* AtomicMarkableReference 的戳是 状态戳
* 两者都可以解决ABA问题
* 前者是通过版本号更新的方式解决ABA问题
* 后者是是状态只能改一次,也即使用一次,即一次性戳,故不存在ABA问题
*/
public class AtomicDemo {
public static void main(String[] args) {
AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100, false);
new Thread(()->{
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "线程的初始标识:" + marked);
// 暂停1s 保证线程B拿到同样的初始标识
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
markableReference.compareAndSet(100,500,marked,!marked);
// 标志是否可以反复修改,如果可以则修改一定成功
boolean b = markableReference.compareAndSet(500, 100, !marked, marked);
System.out.println("标志是否可以反复修改:" + b );
},"A").start();
new Thread(()->{
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName() + "线程的初始标识:" + marked);
// 暂停2s,保证让线程A执行完成
try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(
Thread.currentThread().getName() +
"线程是否修改成功:" +
markableReference.compareAndSet(100,2000,marked,!marked) +
" 修改后的值为: " +
markableReference.getReference() +
" 当前的标志为:" +
markableReference.isMarked()
);
},"B").start();
}
}
四、对象的属性修改原子类
思考:
AtomicInteger和AtomicIntegerFieldUpdater有什么区别?
AtomicInteger和AtomicIntegerFieldUpdater都是的原子类,都能够保证对于一个变量的操作具有原子性,但它们有以下区别:
- AtomicInteger:是一个原子类,能够保证对于一个整型变量的操作具有原子性。它通过CAS(Compare And Swap)操作实现原子性,即先比较目标值是否与预期值相等,如果相等,则将目标值更新为新值。在多线程环境下,CAS操作能够保证对于一个变量的操作具有原子性,从而避免了线程安全问题。AtomicInteger适用于对于整型变量的操作,例如自增、自减等。
- AtomicIntegerFieldUpdater:也是一个原子类,它能够保证对于一个类的某个整型字段的操作具有原子性,不需要将整个类变成线程安全的。它通过反射机制获取类的某个字段的偏移量,然后使用CAS操作实现对于该字段的原子性操作。需要注意的是,这个字段必须是volatile类型的,不能是static类型的。AtomicIntegerFieldUpdater适用于需要对于类的某个字段的操作具有原子性的场景。
举个例子,假设有如下代码:
public class Counter {
private volatile int count;
private static final AtomicIntegerFieldUpdater<Counter> countUpdater = AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
public void increment() {
countUpdater.incrementAndGet(this);
}
public int getCount() {
return countUpdater.get(this);
}
}
这个代码中,Counter类中的count字段是volatile类型的,然后通过AtomicIntegerFieldUpdater获取count字段的偏移量,从而对于该字段的操作具有原子性。increment()方法用于对于count字段进行自增操作,getCount()方法用于获取count字段的值。这样,我们就可以在不将整个Counter类变成线程安全的情况下,对于count字段进行线程安全的操作。
另外,值得注意的是,AtomicIntegerFieldUpdater的使用场景并不是很广泛,通常情况下,使用AtomicInteger即可满足需求。AtomicIntegerFieldUpdater的使用场景通常是在需要对于某个字段的操作具有原子性,但是又不想将整个类变成线程安全的情况下使用。