线程安全之原子性揭秘-《云课堂》

4 篇文章 0 订阅

1.原子操作

原子操作可以是一个步骤,也可以是多个步骤操作,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分(不可中断性)。
将整个操作视作一个整体,资源在该操作中保持一致,这是原子性的核心特征。
下面我们先执行一个例子

public class Counter {
volatile int i = 0;在这里插入代码片

public void add() {
    i++;
}

}
public class Demo1_CounterTest {

public static void main(String[] args) throws InterruptedException {
    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);
}

}

预期结果是:
done…
done…
done…
done…
done…
done…
60000
执行结果是:
第一次
done…
done…
done…
done…
done…
done…
31558
第二次:
done…
done…
done…
done…
done…
done…
27311
其结果不仅与预期结果不一致,每次运行的结果也不一致。
下面我们就来看一下原因:
现将counter反编译 执行javap -v -p Counter.class
会出现
在这里插入图片描述
多线程在jvm的执行流程可参考我上一篇写的https://blog.csdn.net/qq_24045275/article/details/104647714
写的知道其.class在jvm的操作流程图所示:
在这里插入图片描述
其中getfield获取字段值 iconst_1 符号加1 iadd操作加号 从临时操作树栈占顶取值,再将结果值放入栈 从操作树栈放入堆内存。如果多个线程其i值可能每个线程取到的值不会是最新的值,所以就导致了其不会得到预期的结果。

原子操作解决方案:

1.cas(Compare and Swap)
比较和交换。属于硬件同步源语,处理器提供了基本的原子性保证。
cas 操作需要2个数值,一个旧值A(期望操作前的的值)和一个新值B,在操作期间先对旧值进行比较,若没有发生变化,才能交换新值,发生了变化则不交换其无限循环操作。Java 中的sun.misc.Unsafe类,提供了compareAndSwapint(0和compareAndSwapLong等几个方法实现cas这就是所谓的原子性保证线程安全。
在这里插入图片描述
如上图之java通过unsafe不可能直接对内存地址对值修改 知道每一个对象的引用,对象里面标记通过偏移量 (而内存条从硬件角度保证同一时刻只能一个线程)

在这里插入图片描述
如上面的图进行判断 cas操作不正确无线循环重新加载(自旋)
j.u.c的原子包装操作封装类如下:
在这里插入图片描述
用原子解决开头实例代码如下:
public class CounterUnsafe {
volatile int i = 0;

private static Unsafe unsafe = null;

//i字段的偏移量
private static long valueOffset;

static {
    //unsafe = Unsafe.getUnsafe();
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        unsafe = (Unsafe) field.get(null);
            /*获取i字段的偏移量*/
        Field fieldi = CounterUnsafe.class.getDeclaredField("i");
        valueOffset = unsafe.objectFieldOffset(fieldi);

    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}


public void add() {
    //i++;
    for (;;){
        int current = unsafe.getIntVolatile(this, valueOffset);
        if (unsafe.compareAndSwapInt(this, valueOffset, current, current+1))
            break;
    }
}

}
public class Demo1_CounterTest {

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

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

}
其结果:
在这里插入图片描述
其中除了原子操作也可以用锁如:synchronized和lock
其代码如:
public class CounterLock {
volatile int i = 0;

Lock lock = new ReentrantLock();

public void add() {
    lock.lock();

    i++;

    lock.unlock();
}

}
public class CounterSync {
volatile int i = 0;

public synchronized void add() {
    i++;
}

}

3 、automic和
锁的区别:
如下图一个4核的cpu多个线程访问:
在这里插入图片描述
锁机制

在这里插入图片描述
cas的机制
从图中可以看出
sysinorized互斥锁都需要时间损耗, 更节约cpu性能,同一时刻只能一个线程执行,只有一个cpu执行,其他线程阻塞, cpu节省性能

atomic (cas)(对硬件更消耗)cpu跑满了节省消耗时间,用空间换取时间。

4、cas的三个问题

1、循环+cas,自旋的实现让所有的线程都处于高频运行,争抢cpu执行时间的状态。如果操作长时间不成功,会带来很大的cpu资源消耗。
2、仅针对单个变量的操作,不能用于多个变量来实现原子操作。
3、ABA问题。
其中aba问题如下图所示:
!
在这里插入图片描述

如图所示由个线程同时访问同一个内容i的值并且都是通过cas操作,其中假如线程1先访问成功并改变i的值为1,又将值改为0,而这时线程2访问cas(0,1)成功且改变为1结果值并没有影响变化,但是其实在这过程中线程1中一个版本号变了。就如一个企业高管使用了公共的钱,后面有补上去了,虽然钱没变化,但这其中钱不是原来的钱了。
下面我用一个图表示其中变化:
(1)
在这里插入图片描述
(2)
在这里插入图片描述
(3)
在这里插入图片描述
从上面图中知道最后的结果是堆栈中最后只剩下b值了。
图一线程1先将a和b存入堆栈中
图2线程2将a和b取出在讲 c ,d,a 入栈。
图三,就是线程1在对a去堆栈中的a比较,将栈顶指向游离b由于b的next为空,这样就导致了堆栈中就剩下一个b其他的a,d,c丢失了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值