7

In a lot of of the Java source, (for example LinkedBlockingDeque) I see things like this;

final ReentrantLock lock = new ReentrantLock();

public void putLast(E e) throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
       // do stuff
    } finally {
        lock.unlock();
    }
}

I understand the basic pattern (lock, unlock in finally) but my question is why make an assignment to a locally scoped Lock variable before using it? Why do this instead of the following?

final ReentrantLock lock = new ReentrantLock();

public void putLast(E e) throws InterruptedException {
    this.lock.lock();
    try {
       // do stuff
    } finally {
        lock.unlock();
    }
}

Would it affect optimisations? Could the first example prevent lock coarsening?

EDIT after comments: Please don't add an answer if you don't really know why this is the case. This is from the Java source, the @author tag is Doug Lea so I'm pretty sure it's there for a reason. Please don't point out that the code is simply equivalent.

Thanks

Toby
  • 9,523
  • 8
  • 36
  • 59

5 Answers5

7

When you assign to local variable in method, compiler can do some optimizations. see In ArrayBlockingQueue, why copy final member field into local final variable?

Community
  • 1
  • 1
Sergey Gazaryan
  • 1,013
  • 1
  • 9
  • 25
  • Actually the JIT should be quite capable of doing the same optimizations - at least in case of a final variable. So you don't win anything by doing that. The optimization is really only saving a handful bytes(!) - I think 2 per access, minus the assignment. So in that case it's a wash I'd think. – Voo Nov 05 '11 at 20:22
  • @bestsss I can't follow you exactly. The article you're linking yourself states that final CSE IS done in the compiler and what problem it's causing? To quote cliff: "Just to be clear, CSE’ing of final fields had been there for a long time. Charles reports it’s now turned off for Oracle (I haven’t looked at their recent code). I re-wrote the memory aliasing handling in the server compiler recently (doing what I should have done a decade ago!!!), and as a side-effect that beefed up final-field optimizations… which then started triggering these kinds of bugs" – Voo Nov 06 '11 at 00:28
  • @bestsss So it seems that maybe it's deactivated at the moment for hotspot, but it did work for some time it just seems that the latest improvements made those bugs obvious (or caused them?). JITs are complicated.. – Voo Nov 06 '11 at 00:31
  • @Voo, I think it has been disabled for quite long time actually and that was my point. I recall looking at the generated assembler (quite some time ago before cliff's blog) and hotspot used to read the field each time. – bestsss Nov 06 '11 at 00:54
  • @bestsss I remember arguing with a colleague about this optimization some time ago and we wrote some micro benchmarks that showed no measurable improvements - quick and dirty but seems to indicate that it did actually work some time ago for simple cases. So it may be useful for the short/middle term, but in the long run I expect some JSR for lazy initialization that remedies the current situation and makes the final CSE straight forward. Wouldn't do it for my general code, but probably good to know if you really need those last 100 cycles. – Voo Nov 06 '11 at 01:15
2

In this piece of code it doesn't matter: both examples work exactly the same. However if the instance variable lock was not final then it could make a difference as the instance variable could be changed during the locked operation: you then want to make sure that you unlock the same lock that you initially locked.

But as lock is final, it doesn't matter: the local variable assignment in your first example is superfluous.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
2

Digging in the code a little bit I have found examples for both ways by the same author, Doug Lea:

  • LinkedBlockingDeque (since JDK 1.6) uses the "direct access" method.
  • CopyOnWriteArrayList (since JDK 1.5) uses the "local variable" method.

There are more examples for each idiom in java.util.concurrent but it seems, that for each class a consistent style has been chosen.

Please note, that in all relevant cases the lock field has been declared final. That is the most important part, because the memory model semantics for final fields are a bit special in JVM (see JLS).

Building on that: Taking a local copy or not does not affect multithreading correctness.

Also note that Dough Lea has chosen the shorter style in newer code (as shown in the examples). So perhaps the "take a local copy" idiom is some leftover from the days before java.util.concurrent has been part of the JDK and before the JVM memory model has been adopted appropriately. I speculate that the code before that adoption might have looked like this:

public void op(){
    ReentrantLock lock = getLock();
    lock.lock();
    try {
        realOp();
    } finally {
        lock.unlock();
    }
}

where getLock() did contain some crude multithreading safe logic.

A.H.
  • 63,967
  • 15
  • 92
  • 126
2

Hotspot doesn't optimize instance final fields.

In most cases it doesn't matter really, since if the code is compiled and it hits the cache the optimization worth probably 1% however if the code spans though some more code, esp. virtual calls the local load can help predict branching.

I, myself, do the local variable dirty code most of the time if I know/expect it's hot code.

Some further info, incl. Doug Lea personal take on the matter.

bestsss
  • 11,796
  • 3
  • 53
  • 63
  • +1 for the further info link, but if you read it, you'll see that final CSE has existed for quite some time and was maybe now deactivated because some improvements to it made "bugs" obvious, see the comment to Sergey Gazaryan's post for the quotes or just read cliff's comment from `10/28/2011 at 8:47 am` – Voo Nov 06 '11 at 00:33
  • too bad the link's expired – Patrick Parker Jun 09 '20 at 11:55
0

Whilst it shouldn't make any difference, it has been stated on mailing lists that there have been slight measured performance difference on actual JREs.

General advice would still be not to bother with the local. It's only there because performance is really, really crucial in specific code used by a lot of people.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305