Fix a race condition caused by other threads calling mapper methods while mappedStatements are being constructed #2709
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
In projects using the Spring framework, we often use
@PostConstructto perform some operations after bean initialization. In some scenarios, we will create asynchronous thread to perform some time-consuming SQL operations to avoid blocking the application startup process. We observed that the use of mappers in asynchronous thread may broken due to theresizeof the underlyingStrictMapofmappedStatements.Example code for using
@PostConstructwith asynchronous thread, MybatisRaceConditionApplication.java:In this scenario, the main thread is still building the mapper of other beans, that is, it keeps putting
mappedStatementtomappedStatements, and in this asynchronous thread, it keeps gettingmappedStatementfrommappedStatements, becauseStrictMapinherits fromHashMap, when the underlyingHashMapperforms theresizeoperation, before the old array element migration is completed and the asynchronous thread obtains the latesttablereference, At this time, themappedStatementthat has been put before cannot be obtained frommappedStatements.This is the source code of
HashMap#resizein JDK8:In the source code, a new array
newTabis allocated first, and the empty arraynewTabreference is assigned totable. Before the element migration is completed, the asynchronous thread callingmappedStatements.get(id)may not be able to get itmappedStatement(depends on seeing the latesttablereference, note thattabledoes not contain thevolatilemodifier. however, it can be obtained normally before and after resize), instead throws the following exception:The root cause of this problem is that multiple threads access
mappedStatementsconcurrently, and the main thread has made structural modifications tomappedStatements, which does not conform to the usage specification ofHashMap, so in order to solve this problem, we should makeStrictMapinherited fromConcurrentHashMap, at the same time, in order to be compatible with the changes of thecontainsKeymethod and adapt to the NULL key, I rewrote thecontainsKeymethod.This project can reproduce the race condition: GitHub - tianshuang/mybatis-race-condition
Reference:
java - HashMap resize() while single thread writing and multiple threads reading - Stack Overflow
Unit Test
All unit tests pass.