1.什么是原子类
- 不可分割
- 一个操作是不可中断的,即便是多线程的情况下也可以保证
- java.util.concurrent.atomic
原子类的作用
原子类的作用和锁类似,是为了保证并发情况下线程安全。不过原子类型相比于锁,有一定优势:
粒度更细:原子变量可以把竞争范围缩小到变量级别,这使我们可以获得的最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度。
效率更高:通常,使用原子类的效率会比使用锁的效率要高,除了高度竞争的情况
2.Atomic*基本类型原子类,以AtomicInteger为例
- 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) //获取当前的值,并加上预期的值
public final boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public class AtomicIntegerDemo implements Runnable {
private static final AtomicInteger atomicInteger = new AtomicInteger();
public void incrementAtomic() {
atomicInteger.getAndIncrement();
}
private static volatile int basicCount = 0;
public void incrementBasic() {
basicCount++;
}
public static void main(String[] args) throws InterruptedException {
AtomicIntegerDemo r = new AtomicIntegerDemo();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("原子类的结果:" + atomicInteger.get());
System.out.println("普通变量的结果:" + basicCount);
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
incrementAtomic();
incrementBasic();
}
}
}
原子类的结果:20000
普通变量的结果:18488
- AtomicLong 长整型原子类
- AtomicBoolean 布尔型原子类
4.Atomic*Array数组类型原子类
AtomicIntegerArray:提供对int[]数组元素的原子性更新操作。
public class AtomicArrayDemo {
public static void main(String[] args) {
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
Incrementer incrementer = new Incrementer(atomicIntegerArray);
Decrementer decrementer = new Decrementer(atomicIntegerArray);
Thread[] threadsIncrementer = new Thread[100];
Thread[] threadsDecrementer = new Thread[100];
for (int i = 0; i < 100; i++) {
threadsDecrementer[i] = new Thread(decrementer);
threadsIncrementer[i] = new Thread(incrementer);
threadsDecrementer[i].start();
threadsIncrementer[i].start();
}
// Thread.sleep(10000);
for (int i = 0; i < 100; i++) {
try {
threadsDecrementer[i].join();
threadsIncrementer[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < atomicIntegerArray.length(); i++) {
// if (atomicIntegerArray.get(i)!=0) {
// System.out.println("发现了错误"+i);
// }
System.out.println(atomicIntegerArray.get(i));
}
System.out.println("运行结束");
}
}
class Decrementer implements Runnable {
private AtomicIntegerArray array;
public Decrementer(AtomicIntegerArray array) {
this.array = array;
}
@Override
public void run() {
for (int i = 0; i < array.length(); i++) {
array.getAndDecrement(i);
}
}
}
class Incrementer implements Runnable {
private AtomicIntegerArray array;
public Incrementer(AtomicIntegerArray array) {
this.array = array;
}
@Override
public void run() {
for (int i = 0; i < array.length(); i++) {
array.getAndIncrement(i);
}
}
}
AtomicLongArray:提供对long[]数组元素的原子性更新操作。
AtomicReferenceArray:提供对引用类型[]数组元素的原子性更新操作。
5.Atomic*Reference引用类型原子类
AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。
AtomicReference是作用是对”对象”进行原子操作。 提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。
public class AtomicReferenceDemo {
public static void main(String[] args) {
// 创建两个Person对象,它们的id分别是101和102。
Person2 p1 = new Person2(101);
Person2 p2 = new Person2(102);
// 新建AtomicReference对象,初始化它的值为p1对象
AtomicReference ar = new AtomicReference(p1);
//更改p1的id.
p1.setId(106);
// 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
ar.compareAndSet(p1, p2);
Person2 p3 = (Person2)ar.get();
System.out.println("p3 is "+p3);
System.out.println("p3.equals(p1)="+p3.equals(p1));
}
}
class Person2 {
volatile long id;
public Person2(long id) {
this.id = id;
}
@Override
public String toString() {
return "id:"+id;
}
public void setId(long id){
this.id=id;
}
}
- AtomicStampedReference
- AtomicMarkableReference
6.把普通变量升级为原子类
AtomicReference,用于整个对象的更新。但不是每次都必须更新整个对象,有可能我们只需对对象中的某个字段进行原子性修改时,那么就需要使用原子更新字段类
- AtomicIntegerFieldUpdater对普通变量进行升级
public class AtomicIntegerFieldUpdaterDemo implements Runnable{
static Candidate tom;
static Candidate peter;
public static AtomicIntegerFieldUpdater scoreUpdater = AtomicIntegerFieldUpdater
.newUpdater(Candidate.class, "score");
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
peter.score++;
scoreUpdater.getAndIncrement(tom);
}
}
public static class Candidate {
volatile int score;
}
public static void main(String[] args) throws InterruptedException {
tom=new Candidate();
peter=new Candidate();
AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("普通变量:"+peter.score);
System.out.println("升级后的结果"+ tom.score);
}
}
普通变量:19831
升级后的结果20000
AtomicIntegerFieldUpdater 注意点
不同于以往的几个原子更新类,对于 AtomicIntegerFieldUpdater 的使用稍微有一些限制和约束,约束如下:
字段必须是 volatile 类型的,在线程之间共享变量时保证立即可见。
字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
只能是实例变量,不能是类变量,也就是说不能加static关键字。
只能是可修改变量,不能是final变量,因为final的语句就是不可修改。实际上final的语义与volatile是有冲突的,两个关键字不能同时存在。
对于 AtomicIntegerFieldUpdate 和 AtomicLongFieldUpdate 只能修改 int/long 类型的字段,不能修改包装类型。如果要修改包装类型就需要使用 AtomicReferenceFieldUpdate。
- AtomicLongFieldUpdater 原子更新长整型字段的更新器
- AtomicReferenceFieldUpdater 原子更新带有版本号的引用类型
7.Adder累加器
- 是Java 8引入的,相对是比较新的一个类
- 高并发下LongAdder比AtomicLong效率高,不过本质是空间换时间
- 竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性
AtomicLong
public static void main(String[] args) throws InterruptedException {
AtomicLong counter = new AtomicLong(0);
ExecutorService service = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
service.submit(new Task(counter));
}
service.shutdown();
while (!service.isTerminated()) {
}
long end = System.currentTimeMillis();
System.out.println(counter.get());
System.out.println("AtomicLong耗时:" + (end - start));
}
private static class Task implements Runnable {
private AtomicLong counter;
public Task(AtomicLong counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
counter.incrementAndGet();
}
}
}
}
100000000
AtomicLong耗时:1575
public class LongAdderDemo {
public static void main(String[] args) throws InterruptedException {
LongAdder counter = new LongAdder();
ExecutorService service = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
service.submit(new Task(counter));
}
service.shutdown();
while (!service.isTerminated()) {
}
long end = System.currentTimeMillis();
System.out.println(counter.sum());
System.out.println("LongAdder耗时:" + (end - start));
}
private static class Task implements Runnable {
private LongAdder counter;
public Task(LongAdder counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
counter.increment();
}
}
}
}
100000000
LongAdder耗时:225
- 这里演示多线程情况下AtomicLong的性能,有16个线程对同一个AtomicLong累加
- AtomicLong由于竞争很激烈,每一次加法,都要flush和refresh,导致很消耗资源
LongAdder带来的改进和原理
- 在内部,这个LongAdder的实现原理和刚才的AtomicLong是有不同的,AtomicLong的实现原理是,每一次加法都需要做同步,所以在高并发的时候会导致冲突比较多,也就是降低了效率
- 而此时的LongAdder,每个线程会有自己的一个计数器,仅用来在自己线程内计数,这样一来就不会和其他线程的计数器干扰
- LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计算
- base变量:竞争不激烈,直接累加到该变量上
- Cell[]数组:竞争激烈,各个线程分散累加到自己的槽Cell[i]中
源码解析
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。
缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。
8.Accumulator累加器
Accumulator的Adder非常相似,Accumulator就是一个更通用版本的Adder
public class LongAccumulatorDemo {
public static void main(String[] args) {
LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);
ExecutorService executor = Executors.newFixedThreadPool(8);
/**
* 从1累加99
*/
IntStream.range(1, 100).forEach(i ->
executor.submit(() -> accumulator.accumulate(i)));
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println(accumulator.getThenReset());
}
}
4950
使用场景
1.并行计算
2.程序不要求顺序计算