看一个volatile的例子,代码如下,三个线程对同一个变量做累加。
package com.example.multithread.atomicmethod;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class AtomicTest {
private static volatile int result=0;
private static void add(){
result++;
}
public static void main(String[] args) throws InterruptedException {
IntStream.rangeClosed(1,3).forEach(r->{
new Thread(){
@Override
public void run() {
for (int i=0;i<1000;i++){
add();
}
}
}.start();
});
// Thread.yield();
Thread.sleep(2000);
System.out.println("result="+result);
}
}
我们希望上面的代码运行结果为3000,但是多运行几次就会出现小于3000的情况,这是由于变量result进行的++操作为非原子操作,出现了安全问题:多个线程从主内存加载数据到本地高速缓存,基于缓存中的数据做++操作,最后把计算结果刷回主内存。如果不同线程同时从主内存加载了相同数据到本地缓存,基于缓存做操作,就会出现数据安全问题,也就是volatile无法保证原子性。
解决方法一:
给非原子操作加锁(加syncronized锁)。
解决方法二:
使用原子类包装变量,如下,多次运行后结果始终是3000。
package com.example.multithread.atomicmethod;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class AtomicTest {
private static volatile AtomicInteger result= new AtomicInteger(0);
private static void add(){
result.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
IntStream.rangeClosed(1,3).forEach(r->{
new Thread(){
@Override
public void run() {
for (int i=0;i<1000;i++){
add();
}
}
}.start();
});
// Thread.yield();
Thread.sleep(2000);
System.out.println("result="+result);
}
}
使用原子类Atomic包装后的变量是通过CAS(compare and swap)操作来保证变量操作的原子性的。
线程启动时,会从主内存中加载数据到本地缓存中,当线程要进行CAS操作时,会将本地缓存中的值和主内存中值对比,如果两者一样则触发指定操作(如加1,设置为某个值),否则不会触发操作,重新从主内存加载最新的数据,重新CAS。(以上观点仅仅是个人学习原子类后体会,不保证理解到位。)
JUC包中的原子类:
基本类型–>使用原子的方式更新基本类型
AtomicInteger:整型原子类
AtomicLong:长整型原子类
AtomicBoolean:布尔型原子类
数组类型–>使用原子的方式更新数组里的某个元素
AtomicIntegerArray:整型数组原子类
AtomicLongArray:长整型数组原子类
AtomicReferenceArray:引用类型数组原子类
引用类型–>
AtomicReference:引用类型原子类
AtomicStampedReference:原子更新引用类型里的字段原子类
AtomicMarkableReference:原子更新带有标记位的引用类型
对象的属性修改类型–>
AtomicIntegerFieldUpdater:
AtomicLongFieldUpdater:
AtomicStampedReference:
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)//获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update)//如果输入的数值等于预期值,则以原子方式将该值设置为输入值
public final void lazySet(int newValue)//最终设置为newValue,使用lazySet设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。