4.2.2 线程安全之原子性

案例分析

为什么多个线程同时对变量i++会出现非预期结果呢?

/**
如下代码 6个线程同时对变量i做i++的操作,每个线程自增1w次,
结果时随机的且值基本小于6w
*/
public class Demo1_CounterTest {

    public static void main(String[] args) throws InterruptedException {
       // final CounterUnsafe ct = new CounterUnsafe();
        final Counter ct = new Counter();

        for (int i = 0; i < 6; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        ct.add();
                    }
                    System.out.println("done...");
                }
            }).start();

        }

        Thread.sleep(6000L);
        System.out.println(ct.i); //输出的结果是??
    }

    static class Counter {
        volatile int i = 0;

        public void add() {
            i++; //原子性问题,为什么??
        }
    }

}

问题分析(i++操作时非原子性的)

2个线程同时去对i++, 由于i++这个操作时分部进行的 导致t1 ,t2拿到 i=0 然后去做对应的自增及写入操作。

thread1 getFiledI =0 ->压入操作数栈 i=0 -> 执行i++=1 ->putFiled 1 到堆内存
thread2 getFiledI =0 ->压入操作数栈 i=0 -> 执行i++=1 -> putFIled 1到堆内存

怎么解决这个问题? 保证这种情况下同时只能被一个线程访问,等t1 i++完成后 t2再访问。
> 加锁 ->synchronized , ReentrantLock

对于i++来说加锁后岂不是就是同步的了,那用多线程还有什么意义呢

CAS(Compare and swap)
在这里插入图片描述
原子操作:
在这里插入图片描述

CAS (Compare and swap)

在这里插入图片描述

在这里插入图片描述

public class CounterUnsafe {
    volatile int i = 0; //cas 硬件  内存地址 --Long 232323523454235

    private static  Unsafe unsafe =null;//Unsafe类时final修饰的

    private static  long valueOffSet;// 初始话的时用来存储 变量i对应的内存地址

    static {
        try {
//            unsafe = Unsafe.getUnsafe(); **该方法禁止程序直接调用会抛出异常 , 对于属性静止调用的我们可以通过发射机制来获取值**
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);

            Field field1 = CounterUnsafe.class.getDeclaredField("i");
            valueOffSet = unsafe.objectFieldOffset(field1);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    public void add() {
        for(;;) {
            int current = unsafe.getIntVolatile(this, valueOffSet); //**性能 : 分析 解决原子性问题 方式有: 阻塞 或 自旋(cas)   分别损失的是谁? 自旋属于计算损失cpu 阻塞hunt住线程 损失内存(某种意义上也会损失cpu)**
            //**系统中 内存 和  cpu 哪个更容易出现异常或 100% ?cpu更容易出事 内存出问题大概率是程序的问题 ---更多情况下我们会选择损耗内存**
            if (unsafe.compareAndSwapInt(this, valueOffSet, current, current + 1)){ //要么成功,要么失败
                break;
            };
        }

    }
}

JUC包内的原子操作封装类

内部实现即为CAS -用Unsafe类
AtomicBoolean: 原子更新布尔类型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新长整型

AtomicIntergerArray:原子更新整形数组里的元素
AtomicLongArray: 原子更新长整型数组里的元素
AtomicReferenceArray: 原子更新引用类型数组里的元素

AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器
AtomicLongFieldUpdater: 原子更新长整型字段的更新器
AtomicReferenceFieldUpdater: 原子更新引用类型里的字段

AtomicReference:原子更新引用类型
AtomicStampedReference: 原子更新带有版本号的引用类型
AtomicMarkableReference: 原子更新带有标记为的引用类型

在这里插入图片描述

public class CounterAtomic {
    //volatile int i = 0;
    AtomicInteger i = new AtomicInteger(0); //封装了CAS机制.
    public void add() {
        i.incrementAndGet();
    }
}


CAS的三个问题 -为什么cas不受待见

1.循环+CAS,自旋的实现让所有线程多处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源消耗
2. 仅正对单个变量的操作,不能用于多个变量来实现原子操作
3. ABA问题,如下:
在这里插入图片描述ABA 问题是由于仅仅比较值的时候可能在并发情况下与预期不一致的结果,解决方案:添加版本号
代码可以参考: 中aba–此处需要补充

线程安全的概念

在这里插入图片描述

共享资源

在这里插入图片描述

总结:

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值