- java-app - a simple Demo Bank app to demonstrate common multithreading issues. It uses in memory
ArrayList
andThread.sleep(...)
to simulate DataBase operations - load-generator - a module running pre-defined load tests using Gatling.
Requires JDK17+ installed on your machine.
Use
./gradlew clean build && java -jar java-app/build/libs/java-app-1.0.jar
to run the app. Verify app state by running
HTTP GET http://localhost:8080/api/v1/total-assets
request. You will see ten bank accounts, each with 1000 account balance:
{
"totalBalance": "10000.00",
"accounts": [
{
"accountNumber": "1",
"balance": "1000.00"
},
{
"accountNumber": "2",
"balance": "1000.00"
},
...
{
"accountNumber": "10",
"balance": "1000.00"
}
]
}
Run following command:
./gradlew :load-generator:gatlingRun-pl.lunasoftware.demo.threadssync.loadtest.BankTransferSimulation -Dmode=race
This runs a Gatling load test sending 50 RPS for half a minute to transfer random amount of monet between two randomly picked accounts. Executed code can be found in BankController#transferWithRace Executed code uses no threads synchronisation, leading to a race condition. Verify accounts balance after the load test finishes by sending the same
HTTP GET http://localhost:8080/api/v1/total-assets
request. With a bit of luck, you will see corrupted data, similar to this:
{
"totalBalance": "7705.00",
"accounts": [
{
"accountNumber": "1",
"balance": "675.00"
},
{
"accountNumber": "2",
"balance": "401.00"
},
...
{
"accountNumber": "10",
"balance": "914.00"
}
]
}
Total balance of all accounts has changed. Despite the severe bug, code execution is fast:
Run following command:
./gradlew :load-generator:gatlingRun-pl.lunasoftware.demo.threadssync.loadtest.BankTransferSimulation -Dmode=slow
This runs a Gatling load test sending again 50 RPS for half a minute to transfer random amount of monet between two randomly picked accounts. Executed code can be found in BankController#transferSlow Executed code uses naive threads synchronisation, where only one bank transfer can be executed simultaneously, leading to a severe performance issues. The application state is not corrupted, though. Verify accounts balance after the load test finishes by sending the same
HTTP GET http://localhost:8080/api/v1/total-assets
request. You will unchanged total balance of 10000.00:
{
"totalBalance": "10000.00",
"accounts": [
{
"accountNumber": "1",
"balance": "924.00"
},
{
"accountNumber": "2",
"balance": "779.00"
},
...
{
"accountNumber": "10",
"balance": "1362.00"
}
]
}
Code execution has greatly slowed down:
Run following command:
./gradlew :load-generator:gatlingRun-pl.lunasoftware.demo.threadssync.loadtest.BankTransferSimulation -Dmode=deadlock
This runs a Gatling load test sending once again 50 RPS for half a minute to transfer random amount of monet between two randomly picked accounts.
Executed code can be found in BankController#transferWithDeadlock
Executed code uses synchronization on accounts involved in a given bank transfer. Nested synchronized
blocks may lead to a deadlock. With a bit of luck the application will stop working after a few (or several dozen) seconds. It will stuck and will be unresponsive to any HTTP requests due to depleted Tomcat pool thread. 200 threads will wait for each other.
You will see many timed-out requests in Gatling report:
Application will need a restart to make it work again.
Run following command:
./gradlew :load-generator:gatlingRun-pl.lunasoftware.demo.threadssync.loadtest.BankTransferSimulation
This runs a Gatling load test sending yet again 50 RPS for half a minute to transfer random amount of monet between two randomly picked accounts.
Executed code can be found in BankController#transfer
Executed code uses synchronization on accounts involved in a given bank transfer. Nested synchronized
blocks are acquired and released in an ordered fashion, avoiding a deadlock condition.
You can verify correct application state by running the same
HTTP GET http://localhost:8080/api/v1/total-assets
request. You will see correct total balance of 10000.00:
{
"totalBalance": "10000.00",
"accounts": [
{
"accountNumber": "1",
"balance": "673.00"
},
{
"accountNumber": "2",
"balance": "2094.00"
},
...
{
"accountNumber": "10",
"balance": "1564.00"
}
]
}
The response times are also much better:
Results of the simulations greatly depend on you machine performance and generated load by the Gatling. You can change sent RPS in BankTransferSimulation.