3

I asked a similar question the other day but wasn't satisfied with the response, mainly because the code I supplied had some issues that people focused on.

Basically, what is the best practice for locking private members in Java? Assuming each private field can only be manipulated in isolation and never together (like in my Test class example below), should you lock each private field directly (example 1), or should you use a general lock object per private field you wish to lock (example 2)?

Example 1: Lock private fields directly

class Test {
  private final List<Object> xList = new ArrayList<Object>();
  private final List<Object> yList = new ArrayList<Object>();

  /* xList methods */ 

  public void addToX(Object o) {
    synchronized(xList) {
      xList.add(o);
    }
  }

  public void removeFromX(Object o) {
    synchronized(xList) {
      xList.remove(o);
    }
  }

  /* yList methods */ 

  public void addToY(Object o) {
    synchronized(yList) {
      yList.add(o);
    }
  }

  public void removeFromY(Object o) {
    synchronized(yList) {
      yList.remove(o);
    }
  }
}

Example 2: Use lock objects per private field

class Test {
  private final Object xLock = new Object();
  private final Object yLock = new Object();
  private List<Object> xList = new ArrayList<Object>();
  private List<Object> yList = new ArrayList<Object>();

  /* xList methods */ 

  public void addToX(Object o) {
    synchronized(xLock) {
      xList.add(o);
    }
  }

  public void removeFromX(Object o) {
    synchronized(xLock) {
      xList.remove(o);
    }
  }

  /* yList methods */ 

  public void addToY(Object o) {
    synchronized(yLock) {
      yList.add(o);
    }
  }

  public void removeFromY(Object o) {
    synchronized(yLock) {
      yList.remove(o);
    }
  }
}
Community
  • 1
  • 1
XåpplI'-I0llwlg'I -
  • 21,649
  • 28
  • 102
  • 151

4 Answers4

9

Personally I prefer the second form. No other code at all can use that reference (barring reflection, debugging APIs etc). You don't need to worry about whether the internal details of the list tries to synchronize on it. (Any method you call on a list obviously has access to this, so could synchronize on it.) You're purely using it for locking - so you've also got separation of concerns between "I'm a lock" and "I'm a list".

I find that way it's easier to reason about the monitor, as you can easily see all the possible code that uses it.

You may wish to create a separate class purely for use as monitors, with an override for toString() which could help with diagnostics. It would also make the purpose of the variable clearer.

Admittedly this approach does take more memory, and usually you don't need to worry about code locking on this... but I personally feel that the benefit of separating the concerns and not having to worry about whether that code does lock on itself outweighs the efficiency cost. You can always choose to go for the first form if you find that the "wasted" objects are a performance bottleneck for some reason (and after you've analyzed the code in the class you're potentially going to synchronize on).

(Personally I wish that both Java and .NET hadn't gone down the "every object has an associated monitor" route, but that's a rant for a different day.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
2

Example 1 is much better. Since xList are final, they are great for synchronization. There is no need for extra lock objects, unnecessarily complicating code and consuming memory. Only make sure the list itself is never exposed to the outside world breaking encapsulation and thread safety.

However consider:

Community
  • 1
  • 1
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
0

Let's put it this way: the second approach uses more code -- what does that extra code buy you? As far as concurrency, the two are exactly the same, so it must be some other aspect from the bigger picture of your app design.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
0

Even if you're sure the object you are doing the lock will never change I find it more reassuring to use special object just for locking. It makes it more transparent. If the class was to be significantly expanded and/or modified in the future by someone else he might find a reason to make xList non-final without noticing that it's used for locking. This could quickly lead to problems. Thread safety is not trivial and can grow more complex when code evolves so make it as clear and as safe as possible. Cost of having a separate object just for locking is small compared to the cost of diagnosing problems with thread-safety.

Maciej
  • 7,871
  • 1
  • 31
  • 36
  • By the same token, someone could make a modification to the class causing the two objects being locked to be used in tandem, without noticing the locks. The modification would require that a single lock be used from that point on, but the someone doesn't notice it. I'm saying I don't think it's a good excuse. Minimizing risks as you say is truly important, but with concurrent code you're more or less guaranteed to hit trouble if you're going into it without being aware of the concurrency aspects. – Mihai Danila Oct 09 '13 at 19:32