解决Java并发编程中的原子性问题

解决并发编程的原子性问题

原子性是指一个或多个操作不被CPU中断的特性,造成原子性问题的源头是CPU线程的切换,CPU线程的切换可能会发生在每一个CPU指令中,而CPU的切换是依赖系统中断的。

单核场景下解决原子性问题

在单核场景中只要禁止CPU中断就可以解决原子性问题

多核场景下解决原子性问题

在多核场景下,同一时刻可能会存在多个线程同时执行的情况,系统中断只能禁止当前CPU的中断,没有办法阻止其他CPU执行,所以在多核CPU的情况下需要保证同一时刻只有一个线程执行代码,相当于多个线程串行执行,前一个线程执行的结果对于后一个线程是可以见的,保证了多个线程之间的互斥

互斥:同一个时刻只有一个一个线程执行

如何实现互斥

在Java中使用来实现线程之间的互斥。通过使用锁的方式对于临界区代码进行加锁和解锁。锁是一种通用的技术方案,Java语言提供了synchronized关键字,就是一种锁的实现。

synchronized关键字

修饰静态方法

// 修饰静态方法 ,锁定的是当前类.class对象
synchronized static void bar() { 
// 临界区 
}
//相当于
// 修饰静态方法 
synchronized(X.class) static void bar() { 
// 临界区 
}

修饰普通方法

// 修饰非静态方法,锁定的是当前实例对象this
synchronized void foo() { 
// 临界区 
}

修饰代码块

// 修饰代码块,锁定的是obj这个对象
Object obj = new Object()void baz() { 
	synchronized(obj) { 
	// 临界区 
	} 
}

在被synchronized修饰后无论是单核还是多核CPU,都能保证原子性操作,同时也可以保证了可见性,因为根据Java内存模型happens-before原则中对于管程的锁的规则,

管程中锁的规则:对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。

synchronized修饰的临界区具有互斥性,前一个线程的解锁对于后一个线程的加锁是可见的,而对于临界区代码的修改happens-before于解锁操作,根据传递性规则,临界区代码的修改对于后续进入的线程是可见的。

锁和受保护资源的关系

锁和受保护资源的一个合理的关系应该是:受保护资源和锁之间的关联关系是 N:1 的关系,

如何使用锁来保护多个资源?

当我们需要保护多个资源的时候,首先要区分多个资源之间是否存在关联关系。

保护没有关联关系的多个资源

对于没有关联关系的多个资源,我们可以使用多把锁来进行保护,每一个资源都用一把锁来进行保护,也可以使用同一把互斥锁来保护多个对象,但是存在一个问题,就是性能太差,会导致所有的操作都是串行执行。用不同的锁对受保护的资源进行精细化管理,能够提升性能,这种锁还有个名字,叫细粒度锁

保护有关联关系的多个资源

关联关系指的是在都是在一个临界区里边的多个资源,如果在临界区存在多个资源,锁需要将所有的资源都要保护起来

总结

”原子性”的本质是什么?其实不是不可分割,不可分割只是外在表现,其本质是多个资源间有一致性的要求,操作的中间状态对外不可见。例如,在 32 位的机器上写 long 型变量有中间状态(只写了 64 位中的 32 位),在银行转账的操作中也有中间状态(账户 A 减少了 100,账户 B 还没来得及发生变化)。所以解决原子性问题,是要保证中间状态对外不可见。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值