并发编程中的三个问题

一、可见性

可见性(Visibility):是指一个线程对共享变量进行修改,另一个线程立即得到修改后的最新值。

案例演示:一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,另
一个线程并不会停止循环。

/**
 * 目标:演示可见性问题
 * 1.创建一个共享变量
 * 2.创建一条线程不断读取共享变量
 * 3.创建一条线程修改共享变量
 */
public class Test01Visibility {

    // 1. 创建一个共享变量
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        // 2. 创建一条线程不断读取共享变量
        new Thread(() -> {
            while (flag) {

            }
        }).start();

        Thread.sleep(2000);

        // 3. 创建一条线程修改共享变量
        new Thread(() -> {
            flag = false;
            System.out.println("线程修改了变量的值为false");
        }).start();
    }

}

 总结:并发编程时,会出现可见性问题,当一个线程对共享变量进行了修改,另外的线程并没有立即看到修改后的最新值

二、原子性

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

案例演示:5个线程各执行1000次 i++;

/**
 * 目标:演示原子性问题
 * 1.定义一个共享变量number
 * 2.对number进行1000的++操作
 * 3.使用5个线程来进行
 */
public class Test02Atomictity {

    // 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 t : list) {
            // 等到线程结束,再运行其他的,为了让5个线程都运行结束,再让main线程打印结果
            t.join();
        }


        System.out.println("number = " + number);
    }
}

 使用插件反汇编class文件,得到下面的字节码指令:

 

 其中,对于 number++ 而言(number 为静态变量),实际会产生如下的 JVM 字节码指令:

 9 getstatic #18 <com/best/eureka/synchronzied/Test02Atomictity.number>
12 iconst_1
13 iadd
14 putstatic #18 <com/best/eureka/synchronzied/Test02Atomictity.number>

由此可见number++是由多条语句组成,以上多条指令在一个线程的情况下是不会出问题的,但是在多线程情况下就可能会出现问题。

比如一个线程在执行13: iadd时,另一个线程又执行9: getstatic。会导致两次number++,实际上只加了1

总结:并发编程时,会出现原子性问题,当一个线程对共享变量操作到一半时,另外的线程也有可能来操作共享变量,干扰了前一个线程的操作。

三、有序性

有序性(Ordering):是指程序中代码的执行顺序,Java在编译时和运行时会对代码进行优化,会导致程序最终的执行顺序不一定就是我们编写代码时的顺序。

案例演示:jcstress是java并发压测工具https://wiki.openjdk.java.net/display/CodeTools/jcstress

修改pom文件,添加依赖

      <dependency>
            <groupId>org.openjdk.jcstress</groupId>
            <artifactId>jcstress-core</artifactId>
            <version>0.3</version>
        </dependency>
@JCStressTest
@Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "ok")
@Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "danger")
@State
class Test03Orderliness {
    int num = 0;
    boolean ready = false;
    // 线程一执行的代码
    @Actor
    public void actor1(I_Result r) {
        if(ready) {
            r.r1 = num + num;
        } else {
            r.r1 = 1;
        }
    }
    // 线程2执行的代码
    @Actor
    public void actor2(I_Result r) {
        num = 2;
        ready = true;
    }
}

 I_Result 是一个对象,有一个属性r1 用来保存结果,在多线程情况下可能出现几种结果?

情况1:线程1先执行actor1,这时ready = false,所以进入else分支结果为1。

情况2:线程2执行到actor2,执行了num = 2;和ready = true,线程1执行,这回进入 if 分支,结果为4。

情况3:线程2先执行actor2,只执行num = 2;但没来得及执行 ready = true,线程1执行,还是进入else分支,结果为1。

还有一种结果0。

运行测试:

mvn clean install

java -jar target/jcstress.jar

总结:程序代码在执行过程中的先后顺序,由于Java在编译期以及运行期的优化,导致了代码的执行顺序未必就是开发者编写代码时的顺序。

视频教程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值