Usually answer this by saying that I'll use synchronized block in Java or on Database side, I'll use Locking concept.
When we learn about multithreading, we are taught about locks, critical sections, transactions, etc. What we generally are not taught is that these are a last resort. Often you can solve concurrency problems without them, and when you can, you avoid a whole host of potential defects and performance issues (this is one of the reasons functional programming is so popular).
If you are designing a fantasy sports application in which there's a contest which can handle only 100 users. If 99 users are already registered for the contest and multiple users hit the PARTICIPATE button at the same time to become the 100th user, then how will you handle this in your application?
The temptation here is to create a counter, check if it has reached 100, then increment it with every registration. This is not a great approach because it creates lock contention over the counter. All threads will need to lock, inspect, and update it atomically, so threads will block each other waiting for their turn. If you don't wrap your counter check and update together in a critical section or transaction, you risk a TOCTOU situation. And if you acquire the locks in the wrong order, you risk a deadlock. Also, this solution has fault tolerance issues-- for example, if a process manages to increment the counter but throws an exception before it can register the user, it is nearly impossible to recover to a known good state.
Instead, this scenario can be handled very simply with this logic.
When a user registers. store their user name with a datetime stamp specifying when exactly they registered, e.g
INSERT Registration(UserID, RegistrationTime) VALUES (@UserID, GETDATE())
To determine if their registration was accepted, select the top 100 users and see if the user is in it.
SELECT COUNT(*) FROM
SELECT TOP 100 UserID FROM Registration ORDER BY RegistrationTime
WHERE UserID = @UserID
This solution requires no locks or transactions other than the implicit transaction created by your insert statement, which is unavoidable. It's also inherently fault tolerant and much simpler to QA.
If you are designing a chess game, where multiple users are selecting the users of same level of competence to play with them. Then suppose at same time, users A and B choose the user C (to play with) at the same time, then how will you handle this?
Again, some simple logic will avoid the need for excessive locking.
- Allow any number of players to join a game. Record the datetime of when they joined.
- Start the game when there are two or more players
- As the first step in the game, sort the players by the datetime that they joined, then boot the 3rd and later players.
You can certainly solve either of these problems with locks or transactions, but it is also easy to avoid those mechanisms and the challenges that accompany them.