Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object deep locking in multithreading environment

I have below class structures in my project for class A and class B:

class A {
    private String id;
    private List<B> bs = new ArrayList<B>();

    A(String id){
        this.id = id;
    }

    public List<B> getBs(){
        return bs;
    }

    public void setBs(List<B> bs){
        this.bs=bs;
    }

    public void addBs(List<B> bs){
        this.bs.addAll(bs);
    }

    public void addB(B b){
        this.bs.add(b);
    }
}

class B {
    private String id;
    private List<String> tags;

    B(String id){
        this.id = id;
    }

    public List<String> getTags(){
        return tags;
    }

    public void setTags(List<String> tags){
        this.tags = tags;
    }

    public void addTags(List<String> tags){
        this.tags.addAll(tags);
    }

    public void addTag(String tag){
        this.tags.add(tag);
    }
}

Also we have a cache class:

class CacheService {
    private static final ConcurrentHashMap<String, Object> CACHE = new ConcurrentHashMap<String, Object>();

    public static Object get(String id){
        return CACHE.get(id);
    }

    public static void put(String id, Object obj){
        return CACHE.put(id, obj);
    }
}

Now objects for class A and B are created using unique IDs and put in this cache by <id, object> combination. For example:

A a1 = new A("10");
CacheService.put("10", a1);

A a2 = new A("11");
CacheService.put("11", a2);

B b1 = new B("1");
CacheService.put("1", b1);

B b2 = new B("2");
CacheService.put("2", b2);

B b3 = new B("3");
CacheService.put("3", b3);

B b4 = new B("4");
CacheService.put("4", b4);

Also I am putting class B objects in the List<B> inside objects a1 and a2. It is important to note that a unique B object is only put once in any A object:

a1.add(b1);
a1.add(b2);

a2.add(b3);
a2.add(b4);

This way we can have multiple objects for class A and B in the CACHE.

Scenario: Now multiple threads access this CACHE but some of them end up to get class A objects and others class B objects depending upon ID specified by user. These threads actually want to read or update information on these objects.

Question: My requirement is when a thread has accessed an object of class A (for example a1) to update it then no other thread should be able to read or update a1 as well as all class B objects (b1 and b2 in this case) which are added to the List<B> inside object a1 until I am finished with all updates on a1. Please tell me how can I acquire a lock in this scenario?

like image 912
Sohan Lal Avatar asked Oct 22 '12 13:10

Sohan Lal


2 Answers

To have deep copying I suggest you to have a Lock in each instance of A and B and have them both implement some interface with lock() and unlock() methods, where class A will acquire its own lock, and all locks of B's. Then, lock an object before using it, and unlock after you're done.

EDIT: So your course of action would be:

  1. create an interface, let's call it Lockable with two methods: lock() and unlock()
  2. have both A and B implement that interface. Accordingly, your cache will now operate with Lockable instead of Object
  3. add a private field to both A and B

    private final Lock lock = new ReentrantLock();

  4. now implementation of Lockable in B will be just to call same methods on the lock
  5. in A, lock() will acquire local instance of lock, and iterate the list of b's and call their lock() method as well. Same for unlock()
  6. now, every time you get an object from your cache, before doing anything with it, you call lock() on that object and then unlock() when you're done.
like image 193
Denis Tulskiy Avatar answered Sep 19 '22 04:09

Denis Tulskiy


The synchronized keyword will help you with this. You can either declare an entire method as synchronized or you can have synchronized() blocks, which lock on a specific key object which you define. When a synchronized() block is entered, no other blocks which lock with that same key object can be accessed by another thread until the block is exited.

See the Java tutorial on synchronization here.

For your example, you could do either of the following:

public synchronized void addB(B b) {
    this.bs.add(b);
}

OR

... declare a lock object in your class ...

private final Object LOCK = new Object();

... and use it a synchronized() block:

public void addB(B b) {
    synchronized(LOCK) {
        this.bs.add(b);
    }
}

The advantages to using the second over the first is that you can completely, explicitly control which sections of code are locked (rather than just the entire method call). When dealing with concurrency, you want to synchronize as little as possible for efficiency purposes, so using this you can perform synchronization on only the bare minimum.

Also, I'm sure someone will point out that you don't need to explicitly declare another lock object, because you are able to synchronize on the current object using the this keyword. However, this other StackOverflow question and answer sum up the reasons I would not recommend doing so nicely.

like image 39
asteri Avatar answered Sep 17 '22 04:09

asteri