[concurrency-interest] Default Functions for Lock Interface

Brian Goetz brian at briangoetz.com
Tue Oct 8 13:58:25 EDT 2013


So, here's the definitive answer about why we haven't done this yet (it 
was discussed by the 335 and 166 EGs.)

 From an API perspective, improvements like these are obvious.  The code 
is shorter, clearer, and less error-prone.  So it seems like a no-brainer.

However, such an API would have a hidden performance cost, at least 
until some VM work plays out.  Here's why.

When you write a method like

   void withLock(Runnable)

the Runnable is going to have side-effects.  So its going to be a 
capturing lambda -- one that captures variables from its scope:

   withLock( () -> { counter++; } );  // counter is captured

With the current implementation of lambda, evaluating (not invoking) a 
capturing lambda expression will cause an allocation.  Whereas the 
hand-unrolled version:

   lock.lock();
   try { counter++; }
   finally { lock.unlock(); }

does not.  The sort of things people do with locks are generally pretty 
performance-sensitive, so such an API was deemed, at the current time, 
to be an "attractive nuisance."

However, we think it is worthwhile to invest in making such idioms 
suitably performant so that we do not have to make these tradeoffs.  The 
missing piece here is intrinsification of the lambda capture; with this, 
existing optimizations (code motion, escape analysis, inlining, and 
generalized box-unbox elimination) can eliminate the capture cost and 
render this idiom free in most cases.  When we get there, we'll revisit 
APIs like this.

On 10/1/2013 1:24 PM, Nathan Reynolds wrote:
> Here's an example of code using ReentrantLock.
>
> private final ReentrantLock m_lock = new ReentrantLock();
> private       int           m_count;
> private final int           m_capacity;
>
> public boolean reserve()
> {
>     m_lock.lock();
>
>     try
>     {
>        if (m_count >= m_capacity)
>           return(false);
>
>        m_count++;
>
>        return(true);
>     }
>     finally
>     {
>        m_lock.unlock();
>     }
> }
>
> There are several problems with this code.  1)  You have to remember to
> call unlock().  2)  You have to put the unlock() in the finally block.
> 3)  The finally block can't have other exception throwing constructs or
> the lock won't be released on some code paths.  4) The m_lock reference
> shouldn't be allowed to change while a thread is executing inside reserve().
>
> After attending JavaOne and hearing about lambdas and default methods in
> interfaces, I came up with an idea to make writing locked code less
> error prone.  The idea is to provide default methods in the Lock
> interface which would make writing the above code simpler and less error
> prone.  Here's one such method.
>
> public void guard(Runnable guarded)
> {
>     lock();
>
>     try
>     {
>        guarded.run();
>     }
>     finally
>     {
>        unlock();
>     }
> }
>
> With the guard() method available, the reserve() method can be rewritten
> in JDK 8 as such.
>
> public boolean reserve()
> {
>     return(m_lock.guard(() ->
>     {
>        if (m_count >= m_capacity)
>           return(false);
>
>        m_count++;
>
>        return(true);
>     }));
> }
>
> All the aforementioned problems are no longer applicable.  guard() takes
> care of them.  The code is much clearer to understand since the
> boilerplate locking code is gone.  It also might help JIT by reducing
> the amount of cookie cutter code it has to optimize.
>
> Here are the method signatures of other methods which could be added to
> the Lock interface.  The full code is available at
> https://bugs.openjdk.java.net/browse/JDK-8025597.
>
>     public <T> T           guard(Callable<T> guarded) throws Exception
>     public     void        guardInterruptibly(Runnable guarded) throws
> InterruptedException
>     public <T> T guardInterruptibly(Callable<T> guarded) throws
> Exception, InterruptedException
>     public     boolean     tryGuard(Runnable guarded)
>     public <T> Optional<T> tryGuard(Callable<T> guarded) throws Exception
>     public     boolean     tryGuard(Runnable guarded, long time,
> TimeUnit unit) throws InterruptedException
>     public <T> Optional<T> tryGuard(Callable<T> guarded, long time,
> TimeUnit unit) throws Exception, InterruptedException
>
> What do you think?  Please ignore formatting and method names for the
> moment.
>
> --
> -Nathan
>
>
>
> _______________________________________________
> Concurrency-interest mailing list
> Concurrency-interest at cs.oswego.edu
> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>


More information about the Concurrency-interest mailing list