0

I am working on implementing a simple cache using ArrayList in my application.

I would like to synchronize cache update operations, while updating the cache I should not allow to perform read operations. So once cache update is completed, then only cache should allow to read.

ContextManager.java

public class ContextManager{
    private List<String> trashCanIds;
    public List<String> getIds() {
        return ids;
    }

    public void setIds(List<String> ids) {
        this.ids = ids;
    }
}

ConfigManager.java

public class ConfigManager{
    ContextManager ctxManager = new ContextManager();
    public synchronized List<String> loadIds() throws Exception {
        Utils utils = new Utils();
        List<String> listIds = null;
        String[] ids = utils.fetchIds();    
        if(Objects.nonNull(ids) && ids.length > 0) {
            listIds = new ArrayList<>(Arrays.asList(ids[0].split(",")));
        }
        ctxManager.setIds(idsList);
        return idsList;
    }
}

DeleteManager.java

public class DeleteManager {
    ConfigManager configManager = new ConfigManager();
    configManager.loadIds();
}

TestManager.java

public class TestManager {
    ContextManager contextManager = new ContextManager();
    contextManager.getIds();
}

In this code I have synchronized the loadIds() method.

Need help, how to prevent reading getIds() while loadIds() in progress.

1 Answers1

2

You could achieve this by using the ReadWriteLock interface implemented with a ReentrantReadWriteLock instance. This class can represent your case of read and write by acquiring the corresponding lock when performing the getIds and loadIds operations. In fact,

A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing. The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html

Basically, your loadIds should acquire the write-lock before proceeding with its operations. If it succeeds, it immediately acquires the lock and carries on with its computation; otherwise it blocks the corresponding thread until the lock is obtained or an InterruptedException is thrown.

On the other hand, the getIds method should acquire the read-lock instead. Where the current thread immediately obtains the lock if this is available; otherwise it blocks the corresponding thread until the lock is obtained or an InterruptedException is thrown.

ContextManager.java

public class ContextManager{
    private List<String> trashCanIds;
    private ReadWriteLock lock;
    private Lock readLock;
    private Lock writeLock;

    public ContextManager(){
        lock = new ReentrantReadWriteLock(true);
        readLock = lock.readLock();
        writeLock = lock.writeLock();    
    }

    public List<String> getIds() {
        readLock.lock();
        try {
            List<String> tempTrashCanIds = new ArrayList(trashCanIds);
        } finally {
            readLock.unlock();
        }
        return tempTrashCanIds;
    }

    public void setIds(List<String> ids) {
        this.ids = ids;
    }

    public void readLock(){
        this.readLock.lock();
    }

    public void readUnlock(){
        this.readLock.unlock();
    }

    public void writeLock(){
        this.writeLock.lock();
    }

    public void writeUnlock(){
        this.writeLock.unlock();
    }
}

ConfigManager.java

public class ConfigManager{
    ContextManager ctxManager = new ContextManager();
    public List<String> loadIds() throws Exception {
        Utils utils = new Utils();
        List<String> listIds = null;
        String[] ids = utils.fetchIds();    
        if(Objects.nonNull(ids) && ids.length > 0) {
            listIds = new ArrayList<>(Arrays.asList(ids[0].split(",")));
        }

        ctxManager.writeLock();
        try {
            ctxManager.setIds(idsList);
        } finally {
            ctxManager.writeUnlock();
        }
        return idsList;
    }
}
Dan
  • 3,647
  • 5
  • 20
  • 26
  • thanks for the clear answer. Surely I will accept your answer. But I have few clarifications on the code. I have written getters for lock, readLock and writeLock in the contextManager class. And I made below changes in ConfigManager class ctxManager.writeLock() -- ctxManager.getLock().writeLock(); Whether I need to release writeLock() in finally block? added below code in fianlly block. ctxManager.unlock(); --> ctxManager.getWriteLock().unlock(); – Murali krishna Konduru Apr 22 '22 at 09:00
  • I would be careful with using ReadWriteLock without proper performance measurements. If the writes are infrequent, it is probably better to use StampedLock. – alchemist Apr 22 '22 at 09:49
  • @MuralikrishnaKonduru I haven't understood properly your question, but if you're asking me why you need to place your unlock within a finally-block, it is only to ensure that the lock will be release no matter what. Say an exception is raised while you're performing your operation and the read or write lock have been acquired. Nobody else would be able to access those resources anymore because no one will ever release those locks. – Dan Apr 22 '22 at 09:58
  • @alchemist true, write performances might be a problem, but this is intrinsic in its application model since what he/she wants is that no other thread should access the array in write or read mode if any other is already accessing it in write mode. I guess enabling the "fair mode" could decrease the overall throughput if writings are quite frequent, in that case he/she might help it by invoking the empty constructor (or by passing false to the current one). Nonetheless, yours was a correct observation, I agree with you, but I guess this is also part of the application constraints. – Dan Apr 22 '22 at 10:05
  • @Dan In ConfigManager.java there is ctxManager.unlock(); I guess I have to release both readLock and writeLock, because once write is done I have to allow cache reading. so It should be ctxManager.getWriteLock().unlock(); and ctxManager.getReadLock().unlock(); – Murali krishna Konduru Apr 22 '22 at 17:08
  • @MuralikrishnaKonduru I think I must have copied from the wrong file now that I see it properly (weird... I was first writing different attempts). There's a couple of methods missing in the ContextManager class. I've just updated it. Btw you don't need to acquire both read and write locks when you're updating your list. Write lock represents the situation where only one thread can access a resource because is both reading it and updating it (but mainly updating it) which would invalidate any other operation performed by other reading threads. Thta's why the access is exclusive in this context – Dan Apr 22 '22 at 19:12