Atomic原子包
在多线程中,如果要对某个对象的某个属性进行修改,使用锁保证数据的原子性的话,并发很高的时候会大大的降低效率,因为会升级成重量级锁。所以JDK1.5开始提供了Atomic包,不用锁就可以对属性进行原子修改,底层基于魔术类Unsafe提供的三大CAS算法实现:
compareAndSwapObject //CAS修改对象
compareAndSwapInt //CAS修改整型值
compareAndSwapLong //CAS修改长整形值
基于硬件原语-CMPXCHG实现
Atomic包里一共有12个类:
基本类: AtomicInteger、AtomicLong、AtomicBoolean;
引用类型: AtomicReference、AtomicReference的ABA实例、AtomicStampedRerence、AtomicMarkableReference;
数组类型: AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
属性原子修改器: AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、
AtomicReferenceFieldUpdater
四种原子更新方式:
原子更新基本类型、原子更新数组、原子更新引用、原子更新字段
AtomicInteger使用示例:
public class AtomicIntegerTest {
public static int count = 0;
static AtomicInteger atomic = new AtomicInteger(count);
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
atomic.getAndIncrement();
}
countDownLatch.countDown();
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomic.get());
}
}
源码分析:
public final int getAndIncrement() {
//调用unsafe的API
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//var1:对象实例
//var2:值在当前对象实例中的偏移量
//var4:要加的值
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//拿到偏移量的值
var5 = this.getIntVolatile(var1, var2);
//CAS修改值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//如果修改失败,返回偏移量的值
return var5;
}
偏移量: 在当前对象实例的内存区间中,偏离起内存起始位置的值。
例如:
User对象中有个成员变量age,age的偏移量为12,就表示:这个age在当前User对象实例中从内存起始位置 开始算,往后诺12字节,就是age的内存地址,也就是age的偏移量,如果age占了4字节,那么age后边的成员变量的偏移量就是16。通过偏移量可以查询到成员属性在当前对象实例中的值。
查看对象的偏移量:
引入jol-core包可以打印对象实例的内存属性:
ClassLayout.parseInstance(对象实例).toPrintable
原子修改对象类型:
例如Array数组:
AtomicIntegerArray array = new AtomicIntegerArray(整形数组)
array.getAndSet(下标,新值)
AtomicIntegerArray修改的是自己拷贝的内存副本中的值
原子修改属性:
public class AtomicReferenceFieldUpdaterTest {
//表示对User类中,类型为String,属性名为name的值进行原子修改
static AtomicReferenceFieldUpdater atomic = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
public static void main(String[] args) {
User user = new User("张三");
System.out.println("修改前的值为:" + atomic.get(user));
atomic.getAndSet(user, "李四");
System.out.println("修改后的值为:" + atomic.get(user));
}
public static class User {
public volatile String name;
User(String name) {
this.name = name;
}
}
}
CAS修改的ABA问题:
有两个线程A、B同时对内存中F = 1变量进行操作:
1、线程A要修改变量F,它会先去主内存中查询,把F = 1的副本拿到自己的工作内存中。
2、查询完成之后,此时线程B开始执行,拿到了F = 1,但修改之后的值仍然是1。
3、此时线程A把F修改成了2,拿偏移量去内存比对,发现此时内存中F是 1,就修改成功了。
虽然修改成功了,但是此时的F = 1是被线程B修改过的,版本已经不是之前的版本了,这就是ABA问题。
解决方案:
这个问题的解决方案和通过版本号实现乐观锁类似,可以给每次修改操作加一个版本号,修改的时候先对比版本号是否修改过。
JDK1.5中Atomic包中AtomicStampedReference类解决了这个问题:
//在初始值的基础上,新增了一个参数initialStamp来表示初始版本,版本修改之后会进行自加操作
AtomicStampedReference atomicStampedReference = new AtomicStampedReference();
//initialRef:要修改的初始值 initialStamp:初始版本t
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
如果只关心值的结果不关心过程的话,不需要使用这个类来做原子操作。
UnSafe工具类
介绍:
UnSafe工具类主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。例如可以获取对象实例的偏移量做CAS操作、跨方法使用同步锁、使用堆外内存。
unsafe只能通过反射来使用
可通过此工具类来获取Unsafe实例:
public class UnsafeInstance {
public static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
应用场景:
导入文件并发高时,可以通过unsafe申请堆外内存进行操作,用完后必须释放内存
public class UnsafeTest {
public static void main(String[] args) {
Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();
long number = 123123123123L;
byte size = 8;
//分配内存
long memoryAddress = unsafe.allocateMemory(size);
System.out.println("address:->"+memoryAddress);
// number写入到内存
unsafe.putAddress(memoryAddress, number);
//读取内存数据
long readValue = unsafe.getAddress(memoryAddress);
System.out.println("value : " + readValue);
//释放内存
unsafe.freeMemory(memoryAddress);
}
}
输出:
synchronized不能跨方法加锁,但是unsafe可以实现。
public void test(){
//加锁
unsafe.monitorEnter(new Object());
}
public void test1(){
//解锁
unsafe.monitorExit(new Object());
}