并发如何产生
当存在共享的资源,并且存在多个对共享资源访问和修改者,就存在并发问题
更新丢失
很好理解,比如A对数据库的某一条记录进行读取,并计划对该记录进行保改。与此同时,B也读取了该记录,并且对该了进行了修改。B在A读取之后进行读取,但在A修改之前提交了对记录的修改。当A再提交修改时,会覆盖B的修改,也就是B的修改丢失了。
那么怎么能避免这种情况发生。我们可以为每一条记录创建一个版本号,A和B读取记录时版本号为1,当B提交了修改后,版本号加1变成了2。当A再想提交修改,发现版本号已经变了,此时可以抛异常,丢掉A的修改,让A重新读B修改后的数据进行修改提交。
这有点类似版本控制系统,当提交代码修改时,如果版本冲突,就指出冲突的地方,让后提交者解决冲突后再提交。而在我们系统,很难做到两次记录修改的比较,然后让后提交者解决冲突再次提交。而是,我们直接丢弃后修改的,让后提交者从新读取新数据进行修改再提交。
不一致读
这个也很多理解,就是两个人同时读取一份共享的读据,当其中一个人对数据进行修改时,另一个读到的还是旧的数据,不一致读也叫脏读。
那么怎么解决这个问题,这个在DDD里也有比较好的解决思路,就是用事件在处理这种脏读问题。对数据的每一个增加修改删除,都有对应的一个事件。例如当一个数据被修改后,一个Updated事件读抛出,事情的监听者在监听到事件发生后,以某种方式通知所有读取方数据已经被修改,让读取方决定是否要加载最新的数据。
隔离和不变性
避免并发问题产生有两种方式,一种是隔离,一种是不变性。
- 不变性
如果共享资源是不可变的,也即资源一旦产生就不可或者几乎不会再改变,那么在使用资源的时候就可以不考虑并发问题了。显然,要所有的资源是不可变的那几乎不可能,系统都是要通过对资源进行修改来完成各种业务。这就需要我们努力识别出不变的数据,一旦识别出不变的数据,那么这些数据就是线程安全的。别一种观点是将读取分离出来,即读取的永远都是数据的副本,并且副本永远不做修改操作。
- 隔离性
当资源是隔离的,也就是不共享的,那么也不存在并发问题。就像每个进程的资源是不共享的,进程间可以不考虑并发问题。很多时候,我们也的确存在数据的隔离性。
并不是说数据要完全分开两个地方存放才有隔离性,隔离并不是指物理隔离。比如订单,对于用户订单来说,用户所属的订单通常都是用户自己在访问,所以对用户提交和修改订单,我们在某种程度上来说订单数据有一定的隔离性,可以忽略并发性。但对于库存,由于库存是所有用户共享的,数据不存在隔离性,所以对于库存的操作需要考虑并发性。当然,这里并不是说订单可以完全忽略并发性,这里只是举个例子。当数据的并发可能性很小,并且当真的产生并发问题后所带来的后果并不严重,而考虑并发性又带来很多不利的地方,那么我们就暂且忽略并发问题。