并发编程1--并发问题的三个陷阱

一、什么是并发

做饭的时候,你需要洗菜,切菜,烧水,炒菜,作为单核CPU的你,可以选择按照顺序完成这些任务,也可以在烧水的同时洗菜,切菜。对应操作系统,就是CPU在烧水进行的时候不等待(阻塞)水烧好了再做下一步,而是直接去(线程切换)洗菜切菜。好了,并发就是同时进行多件事情的操作(多线程),并发完成任务会更快,CPU利用率更高。

二、并发一定更快吗

还是烧水这个例子,从烧水到去洗菜的地方,也需要你走过去,这也是需要时间成本的,换言之,CPU在响应中断,切换线程的过程中也是需要消耗资源的,当消耗的资源大于并发带来的收益的时候,并发也就不如串行执行快了。

三、并发编程的陷阱

1、可见性问题

对于两个线程操作同一个变量,线程一已经把菜切过了,但是线程二不知道,还去切一次,这就蠢了,保证线程之间变量的可见性,就是可见性问题。
那么可见性问题如何引起的呢?我们知道CPU是有缓存的,单核时代所有线程都操作的一个CPU,都是操作的一个缓存,但是现在是多核的CPU,每个CPU都有自己的缓存。SO,CPU1上缓存的变量A变了,但是CPU2上的变量A还是原来的值,这就引起了可见性问题。 总结,一个线程修改了共享变量的值,其他线程要能读到修改后的值,这就是可见性。
测试代码:

public class test1 {
    
    private static boolean stop = false;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("A run..");
                while (!stop);
                System.out.println("A stop..");
            }
        }).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B run..");
                stop = true;
                System.out.println("B stop..");
            }
        }).start();
    }
}

输出:

A run..
B run..
B stop..

A 线程并没有读到stop变量已经发生变化。

private volatile static boolean stop = false;

如果stop变量加上volatile(保证变量可见的关键字!!)来修饰stop,输出就变成

A run..
B run..
A stop..
B stop..

2、原子性问题

以count自加举例来说明,在java层面count++是一句话,但是由JVM翻译到机器语言上后,就不是一句话了,变量count从内存加载到cpu寄存器,然后在寄存器执行+1动作,在把结果写入内存。线程一刚把count=0加载到cpu寄存器上,线程二也来把count=0加载到cpu寄存器上,各自加了一个1,再先后把count写到内存。此时count就=1,而不是2。这就是原子性问题,在cpu执行过程中,不被中断,能够保证连续性,叫做原子性。
测试代码:10个线程并发对count自加,预期结果应该是100000

public class yuanziwenti {

    private long count = 0;
    private void adds(){
        for (int i = 0; i < 10000; i++) {
              count++;
//            count.incrementAndGet();
        }
    }

    public static void main(String[] args) {
        final yuanziwenti yuanziwenti = new yuanziwenti();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    yuanziwenti.adds();
                }
            }).start();
        }

        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(yuanziwenti.count);
    }
}

输出结果:

98976  //而不是100000

如果把long改成AtomicLong,输出结果就是100000。(如果不是10000,可能是可见性问题导致的)
因为AtomicLong是专门保证long型变量原子性的!

import java.util.concurrent.atomic.AtomicLong;

public class yuanziwenti {

    private AtomicLong count = new AtomicLong(0);
    private void adds(){
        for (int i = 0; i < 10000; i++) {
            count.incrementAndGet();
        }
    }

    public static void main(String[] args) {
        final yuanziwenti yuanziwenti = new yuanziwenti();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    yuanziwenti.adds();
                }
            }).start();
        }

        try {
            Thread.sleep(7000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(yuanziwenti.count);
    }
}

3、有序性问题

有序性的问题是因为编译器为了优化性能,有可能改变程序中的执行顺序。JAVA实现单例模式的时候会通过双重检查创建单例对象

参考单例模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

obession

觉得有用可以打赏咖啡一杯~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值