
课程咨询: 400-996-5531 / 投诉建议: 400-111-8989
认真做教育 专心促就业
并发编程开发是目前大多数软件开发程序员都需要学习的一种编程开发方式,而本文我们就通过案例分析来简单了解一下,Java并发编程特性分享。
现代的CPU架构基本有多级缓存机制,t线程会将run加载到高速缓存中,然后主线程修改了主内存的值为false,导致缓存不一致,但是t线程依然是从工作内存中的高速缓存读取run的值,终无法跳出循环。
可见性
正如上面的例子,由于不做任何处理,一个线程能否立刻看到另外一个线程修改的共享变量值,我们称为"可见性"。
如果在并发程序中,不做任何处理,那么就会带来可见性问题,具体如何处理,见后文。
有序性
有序性是指程序按照代码的先后顺序执行。但是编译器或者处理器出于性能原因,改变程序语句的先后顺序,比如代码顺序"a=1;b=2;",但是指令重排序后,有可能会变成"b=2;a=1",那么这样在并发情况下,会有问题吗?
在单线程情况下,指令重排序不会有任何影响。但是在并发情况下,可能会导致一些意想不到的bug。
全屏
假设有两个线程A、B同时调用getInstance()方法,正常情况下,他们都可以拿到instance实例。
但往往bug就在一些极端的异常情况,比如newSingleton()这个操作,实际会有下面3个步骤:
分配一块内存M;
在内存M上初始化Singleton对象;
然后M的地址赋值给instance变量。
现在发生指令重排序,顺序变为下面的方式:
分配一块内存M;
将M的地址赋值给instance变量;
后在内存M上初始化Singleton对象。
优化后会导致什么问题呢?我们假设线程A先执行getInstance()方法,当执行完指令2时恰好发生了线程切换,切换到了线程B上;如果此时线程B也执行getInstance()方法,那么线程B在执行一个判断时会发现instance!=null,所以直接返回instance,而此时的instance是没有初始化过的,如果我们这个时候访问instance的成员变量就可能触发空指针异常。
这就是并发情况下,有序性带来的一个问题,这种情况又该如何处理呢?
当然,指令重排序并不会瞎排序,处理器在进行重排序时,必须要考虑指令之间的数据依赖性。
原子性
比如你认为的一行代码:count+=1,实际上涉及了多条CPU指令:
指令1:先,需要把变量count从内存加载到CPU的寄存器;
指令2:之后,在寄存器中执行+1操作;
指令3:后,将结果写入内存(缓存机制导致可能写入的是CPU缓存而不是内存)。
操作系统做任务切换,可以发生在任何一条CPU指令执行完。假设count=0,如果线程A在指令1执行完后做线程切换,线程A和线程B按照下图的序列执行,那么我们会发现两个线程都执行了count+=1的操作,但是得到的结果不是我们期望的2,而是1。
我们潜意识认为的这个count+=1操作是一个不可分割的整体,就像一个原子一样,我们把一个或者多个操作在CPU执行的过程中不被中断的特性称为原子性。但实际情况就是不做任何处理的话,在并发情况下CPU进行切换,导致出现原子性的问题,我们一般通过加锁解决,这个不是本文的重点。
【免责声明】:本内容转载于网络,转载目的在于传递信息。文章内容为作者个人意见,本平台对文中陈述、观点保持中立,不对所包含内容的准确性、可靠性与完整性提供形式地保证。请读者仅作参考。更多内容请加danei456学习了解。欢迎关注“达内在线”参与分销,赚更多好礼。