并发编程 Atomic、Unsafe学习总结

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());

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值