Java并发编程-并发问题从哪儿来

并发问题的根本

围绕着原子性、有序性、可见性,会出现各种的并发问题,要理解透彻这三种特性,才能有效的定位出现的并发问题(并发问题往往是综合性的)

可见性

例如:不同的CPU缓存造成的可见性问题

假设一个场景,调用add方法,count+=1执行1w次,在代码编译后,CPU指令为如下3步骤
1、将count读到cpu
2、进行+1操作
3、count写回内存中(有可能写入cpu缓存,但cpu缓存再写入内存时间不可控)

现在线程A、B同时执行add方法,执行结果理想为count=2w,但实际结果往往是0-2w之间。

AB
COUNT读到内存(count=0)COUNT读到内存( count=0 )
+1+1
count写回内存中count写回内存中

同时将count=0读到cpu,结果导致执行完,本省应该=2,但=1

由此可见,不同CPU由于对变量数据不可见,并且操作不是原子性,数据更新不及时,会导致出现并发问题

原子性

切换CPU导致的原子性问题

原子性,一个操作不是原子性,那么在并发环境下,往往是不可靠的
例如上面的问题

有序性

cpu执行指令前,会对待执行指令进行重排序优化,我们使用的都是高级语言进行编程,很多语句,在编译后并不按照理想的流程排序。

例如Java语言的二阶段判断,保障获取到单例实例,不会导致后边的程序执行空指针。

通常如下:

class Singleton{
	static Singleton instance;
	static Singleton getInstance(){
		if(instance==null){
			synchronized (instance){
				if(instance==null){
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

理想情况下,A、B两个线程,同时执行getInstance(),A发现instance未初始化,拿到锁,去初始化,B等待A初始化完成后释放锁,拿到锁资源,判断,已经初始化,直接返回结果。

但是,毕竟是理想情况下,这个情况,我们都是假定,初始化流程如下才可:

  1. 开辟内存空间
  2. 根据对象类型初始化
  3. 将内存地址指向变量instance

如果,代码在编译后,指令进行重排序,则会为

  1. 开辟内存空间
  2. 内存地址指向变量instance
  3. 根据数据类型初始化

哎嗨!这就坏菜了

A执行到2步骤,B进行第一个判断,发现已经指向内存地址,变量对象不为空,直接返回。

但是此时对象未被初始化,调用获取属性的方法,会抛出异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值