Reputation: 844
In my program, I will be storing each "block" of data in a separate file. Multiple threads will both read from and write to various files, and I want to avoid possible problems from not synchronizing this properly. Essentially, I want a setup where each file behaves as if it has its own ReadWriteLock
(Two threads can write to two different files concurrently, but not to the same file). I could do just this (And do some optimizations where ReadWriteLock
s are only instantiated when they are needed and destroyed when not), but I'm not sure that this is the "proper" thing to do. Is there a better way to do this?
I've read that using a FileLock
would not be the right thing to do in this situation, because it only matters between different processes, and would not do anything in this case because all of my threads are in the same process.
To summarize: What I want to avoid is having one thread reading from one file locking out every other thread from doing anything with the many other files.
Upvotes: 0
Views: 1800
Reputation: 253
Here's an example singleton class that provides most of the functionality I think you are describing. It maintains a map of file names to ReadWriteLocks and allows callers to acquire/release a lock based on a file name and a READ/WRITE access type. The class instantiates locks on first use, and hides lock objects from the caller, which should help you to safely clean up locks that are not in use.
public enum LockRegistry {
// make our class a singleton
INSTANCE;
// map of file names to locks - You may want to change the keys to be File or
// something else, just be wary of the class's hashCode() semantics
private Map<String, ReadWriteLock> lockMap = new HashMap<String, ReadWriteLock>();
// lock to protect our registry - helps to prevent multiple threads
// from instantiating a lock with the same key
private Lock registryLock = new ReentrantLock();
// allow callers to specify the lock type they require
public enum LockType {
READ, WRITE
}
public void acquire(String fileName, LockType type) {
// lazily instantiates locks on first use
ReadWriteLock lock = retrieveLock(fileName);
switch (type) {
case READ:
lock.readLock().lock();
break;
case WRITE:
lock.writeLock().lock();
break;
default:
// handle error scenario
break;
}
}
public void release(String fileName, LockType type) {
ReadWriteLock lock = retrieveLock(fileName);
switch (type) {
case READ:
lock.readLock().unlock();
break;
case WRITE:
lock.writeLock().unlock();
break;
default:
// handle error scenario
break;
}
}
private ReadWriteLock retrieveLock(String fileName) {
ReadWriteLock newLock = null;
try {
registryLock.lock();
newLock = lockMap.get(fileName);
// create lock and add to map if it doesn't exist
if (newLock == null) {
newLock = new ReentrantReadWriteLock();
lockMap.put(fileName, newLock);
}
} finally {
registryLock.unlock();
}
return newLock;
}
}
And here is a simple test scenario to show the LockRegistry in action:
public class LockTester implements Runnable {
private int id;
private String fileName;
private LockType type;
public LockTester(int id, String fileName, LockType type) {
this.id = id;
this.fileName = fileName;
this.type = type;
}
@Override
public void run() {
try {
System.out.println("Consumer" + id + " acquiring " + type + " for "
+ fileName);
LockRegistry.INSTANCE.acquire(fileName, type);
System.out.println("Consumer" + id + " holding " + type + " for "
+ fileName);
// hold the lock for 2 seconds
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
LockRegistry.INSTANCE.release(fileName, type);
System.out.println("Consumer" + id + " release " + type + " for "
+ fileName);
}
}
public static void main(String[] args) {
List<Thread> testThreads = new ArrayList<Thread>();
testThreads.add(new Thread(new LockTester(1, "file1", LockType.READ)));
testThreads.add(new Thread(new LockTester(2, "file1", LockType.READ)));
testThreads.add(new Thread(new LockTester(3, "file1", LockType.WRITE)));
testThreads.add(new Thread(new LockTester(4, "file1", LockType.WRITE)));
testThreads.add(new Thread(new LockTester(5, "file2", LockType.WRITE)));
testThreads.add(new Thread(new LockTester(6, "file3", LockType.WRITE)));
testThreads.add(new Thread(new LockTester(7, "file4", LockType.WRITE)));
for (Thread t : testThreads) {
t.start();
}
}
}
Hope this helps.
Upvotes: 2