并发问题的定义与锁的思考

一直在思考究竟怎样的场景才算并发问题,之前我对并发问题的理解就是多个线程访问一个临界资源,如果不做并发控制,临界资源会产生各种不同的情况。理解也停留在多个线程执行变量自增,自增操作是由几个程序序列组成的程序块,主存取数-CPU缓存操作-放回主存,由于不同线程执行序列互相交错(暂不考虑单个线程在JIT上的指令重排),就会产生各种诡异的错误。

但后来在实习以及实践中,发现大家谈论的并发问题不只是停留在一个变量自增的指令执行上,更多的是多个线程的一系列程序序列对某个变量进行操作,其实和上面的单一变量自增也是相似的。基本要素就是多个处理流程(多进程或者多线程),需要访问一个临界资源(这个资源可以是库存也可以是别的东西),每个处理流程有一个由多个程序序列组成的程序块,来操作这个临界资源,程序块中的多个序列会互相交错执行(由于CPU运行速度不同或者是sleep导致多个线程的程序块没有完整地一遍执行完),最终产生了不同的结果。

常用的解决方案就是,锁住的范围是程序序列开始的第一行代码到程序序列的最后一行代码,不同线程在访问此程序块前需要获得锁,锁具有互斥性。这就可以将几个程序序列组成的程序块串行化,从而避免序列执行交错而导致的并发安全问题,因为这相当于将第一行程序序列到最后一行程序序列之间的执行动作锁成了一个整体,也就是保证了程序块的原子性。当然这样子势必会牺牲多核并行运行的高效率,如果要利用多核并行的高效率同时保证并发安全性,就要避免多个处理流程操作同一个有状态的临界资源,即多个处理流程各自在意的变量状态是完全无关的。

在单JVM层面多线程上的,可以用synchronized、lock等锁去控制;涉及到分布式集群上面的,就需要引入分布式锁去保证多进程的并发安全。上面所有的安全控制,本质上都是在维护几个程序序列所组成程序块之间的串行化,进一步发散开来,串行化不只是可以通过加锁控制,甚至可以通过引入第三方组件如消息队列,保证两个节点执行程序块的串行化,这一点也和单机上多进程通过内核队列可以保持程序块同步不谋而合。

注意锁不要和事务搞混,事务可以和锁放在一起写,事务强调几个程序序列组成的程序块的一致性和原子性,会在程序块执行出错时回滚各个程序序列,保证程序块的事务原子性;而锁更多是保证多个序列组成的程序块整体的串行化。

更新:后续从大佬上也学到,我们并不一定要从技术层面上去解决并发问题,可以从业务设计的角度解决并发问题。比方说评论留言查改的并发问题,别人留言评论的时候,楼主刚好把主帖子删了,导致留言评论不存在。业务设计上可以在删除时选择逻辑删除(只是打上删除的标记位),记录在数据库中保留六个月以上,这样子尽可能减少并发问题概率。因为记录六个月的记录,在被清理时刚好有人评论的概率是比较低的。还有比如说库存扣减问题,当库存告急的时候直接手动去修改库存数字肯定是有并发问题的,这里不能保证库存读和写的原子性。解决办法就是可以逻辑上有a/b两个库,当一个库存告急时切换到另一个库,待这个库补足以后再切换回去,避免同时扣减和增加库存。

你可能感兴趣的:(并发编程,java,开发语言)