jpf-examples/ ├── pom.xml ├── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ ├── distributedcache/ │ │ │ ├── DistributedCache.java │ │ │ ├── InvalidateTask.java │ │ │ └── ReadTask.java │ │ └── pubsub/ │ │ ├── MessageProcessingPubSub.java │ │ ├── Producer.java │ │ └── Consumer.java └── README.md
In many real-world applications, a shared cache resource is accessed by multiple threads performing different operations. The typical operations in our scenario include:
In my design, I intentionally introduce a deadlock condition by using two distinct ReentrantReadWriteLock
instances:
invalidate()
operation acquires lock1
first and then lock2
.read()
operation acquires lock2
first and then lock1
.When two threads invoke these operations concurrently, each thread may end up waiting indefinitely for the other lock to be released, thereby causing a deadlock.
I structured the example into several components, each serving a specific purpose:
DistributedCache.java:
This is the primary class where the shared cache and two locks are declared. It contains the methods invalidate()
and read()
that simulate the operations on the cache.
InvalidateTask.java and ReadTask.java (Optional):
While the main class may start the threads directly, I have optionally modularized the operations into separate task classes. Each class implements the corresponding operation in its run()
method. This modularization aids in clarity and testing.
Main Execution Class:
The main()
method is used to instantiate the cache and spawn two threads: one for cache invalidation and one for cache reading. This simulates the concurrent environment that can lead to deadlock.