Loading
0

原子性问题

内容纲要

现在咱们来看并发编程中第二个问题,原子性问题。那么咱们的目标学习什么是原子性问题?在学习的过程中,咱们分成两步,第一步介绍原子性的概念,第二步通过一个案例来演示原子性问题。

咱们先来看原子性的概念,我们也是要先看一下前提,首先第一个啊也是要有多个线程,如果一个县城没有竞争是看不出问题的。第二我们要有共享变量,到时候多个线程来对共享变量操作。

原子性是指在一次或多次操作中,要么所有的操作都执行并且不会受其他因素的干扰而中断,要么所有的操作都不执行。

案例:

目标:演示原子性问题

步骤:

1、定义一个共享变量 number。

2、对 number 进行 1000 的 ++ 操作。

3、使用 5 个线程来进行。

public class AtomicityTestDemo01 {
​
    // 1、定义一个共享变量 number
    private static int number = 0;
​
    public static void main(String[] args) throws InterruptedException {
​
        // 2、对 number 进行 1000 的 ++ 操作
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {
                number++;
            }
        };
        List<Thread> list=new ArrayList<>();
        // 3、使用 5 个线程来进行
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }
​
        for(Thread thread:list){
            thread.join();
        }
        System.out.println("number="+number);
​
    }
}
复制代码

按照我们的想法,五个线程,每个线程执行一千次 i ++,正常来说应该等于五千,咱们来运行看效果,有可能是 5000,也有可能会小于5000。程序运行结果如下图所示。

image.png 那么为什么会少了很多呢?那么这是由于i ++ 它是多个操作。而且是由多线程来操作的,那么并没有保证i ++ 的这个原子性。咱们通过反汇编的方式,来看看这个 i ++ 到底是有几个步骤来组成的。

我们用命令行打开,其他命令行也可以,可以通过字节码文件进行反汇编,看到一些字节码的指令。

javap -p -v 
复制代码

-p:就显示这种私有的,-v:就是详细信息也显示出来。 咱们的 number ++ 对应字节码指令啊是有 4 句。由此咱们就可以看到 number ++ 是由四条指令来组成的。一个线程是没有问题的,但是在多线程的情况下是存在原子性问题的。

image.png

咱们来分析一下这个原子性问题,如下图所示。那么我们以后都做一个约定。让这个红色的箭头代表主线程。

image.png

咱们程序呢是由主线程从 main 方法开始执行的,然后 Runnble 先不会执行,先执行创建出 5 个线程,在去执行 Runnble 。这里 5 个线程分析起来有点麻烦。我们假设这里创建了两个线程,这两个线程呢,它都来执行 number ++ 的操作。另外我们假设此时的 number 的值是 0。假设第一个线程(t1 线程)先执行 number ++,有四条指令,第一条指令 getstatic,其实是取到 number 值 0。往下执行一步,到达指令 iconst_1,这是在准备一个常量 0。假设再往下面执行一步,要去执行指令 iadd,让 number = 0 去跟1相加,最终的结果是 1。

但是虽然此时并没有赋值,CPU 切到另外一个线程(t2 线程)去执行。那么另外一个线程也来执行number++,也有四条指定要执行。先执行第一条 getstatic,是不是取这个 number 的值,目前值没能改变还是 0。那么取到了之后,线程往下执行指令,准备了一个 1,然后执行 iadd 指令, t2 线程的值也变成了 1。假设这个线程再往继续执行执行,执行指令 pustatic,它就会把这个 1push 就赋给了number。此时 t2 线程线程执行完了。假设cpu 又切到 t1 线程,它也往执行,也要执行指令 pustatic ,可是把 1push 过去,所以 number 值还是 1。

这时候咱们就看到了两个线程来执行 number ++,按道理 number 的值应该是 2,因为这里有四条指令没有保证原子性,发现最终结果只加了一次 1,就让数据产生的错误。

那么这个问题的原因就是有两个线程来操作 number ++,它是多条语句啊,其中一个线程执行到一半的时候,然后另外一个线程又来执行了,就第二个线程干扰了第一个线程的执行,所以就没有保证原性。

小结:我们在并发编程的时候,很有可能会出现原则性问题。当我们一个线程对共享变量操作到一半的时候啊,那么另外一个线程呢也有可能来对共享变量进行操作,会干扰前一个线程的操作,让前一个线程的操作那个没有保证原子性。