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
@PostConstruct
to 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 theresize
of the underlyingStrictMap
ofmappedStatements
.Example code for using
@PostConstruct
with asynchronous thread, MybatisRaceConditionApplication.java:In this scenario, the main thread is still building the mapper of other beans, that is, it keeps putting
mappedStatement
tomappedStatements
, and in this asynchronous thread, it keeps gettingmappedStatement
frommappedStatements
, becauseStrictMap
inherits fromHashMap
, when the underlyingHashMap
performs theresize
operation, before the old array element migration is completed and the asynchronous thread obtains the latesttable
reference, At this time, themappedStatement
that has been put before cannot be obtained frommappedStatements
.This is the source code of
HashMap#resize
in JDK8:In the source code, a new array
newTab
is allocated first, and the empty arraynewTab
reference 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 latesttable
reference, note thattable
does not contain thevolatile
modifier. 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
mappedStatements
concurrently, 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 makeStrictMap
inherited fromConcurrentHashMap
, at the same time, in order to be compatible with the changes of thecontainsKey
method and adapt to the NULL key, I rewrote thecontainsKey
method.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.