Groovy Concurrency With Closure Locks

I have played around a bit more with closures and locks in Groovy.

In one of Venkat S’s talks, he says that the java.util.concurrent.locks package solves a lot of the issues with the synchronized keyword: You have to have try/catch/finally blocks to get the lock and release it. I made a class that will hold a Lock object, and can take code as a closure and run it. It seems to work pretty well. At least I think so.

But I realized there is one possible issue: The ClosureLock class only imports from java.util.concurrent.locks. What if some of the code in the closure is from another package that is not imported in the ClosureLock class?

I made a class that imports groovy.time.Duration, and creates a couple of objects and invokes methods in a closure. And it ran without blowing up. I also made a class called DebugClosureLock that prints out some stuff to the console checking if the lock is held by the current thread.

Here is the DebugClosureLock class:

package info.shelfunit.concurrency.locks

import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock

class DebugClosureLock { 
    final Lock lock = new ReentrantLock()
    
    // prevent lock from being changed
    def setLock( arg ) {}
    
    def lockSomeCode( block ) {
        
        try { 
            println "Is lock ${lock.hashCode()} held? ${lock.isHeldByCurrentThread()}"
            println( "about to try to get the lock" )
            if ( lock.tryLock() || lock.tryLock( 500, TimeUnit.MILLISECONDS ) ) {
                println " About to call block. Is lock ${lock.hashCode()} held? ${lock.isHeldByCurrentThread()}"
                block.call()
            }
        } finally {
            println( "About to try to unlock" )
            lock.unlock()
        }
        println "Done with code. Is lock ${lock.hashCode()} held? ${lock.isHeldByCurrentThread()}"
    }
}

Here is the class that uses it:

package info.shelfunit.concurrency.locks

import groovy.time.Duration 

class SecondLockRunner { 

  def doStuff() { 
    DebugClosureLock cLock = new DebugClosureLock()
    def dur = new Duration(  30, // days 
        2, // hours 
        45, // minutes 
        32, // seconds 
        123 // millis 
    )
    println( "about to call dur.toString() " )
    cLock.lockSomeCode( { println "${dur.toString()}" } )
    def dur2 = new Duration(  3, // days 
        20, // hours 
        43, // minutes 
        31, // seconds 
        522 // millis 
    )
    println( "\nabout to call dur2.toString() " )
    cLock.lockSomeCode { 
        println "${dur2.toString()}"
        println "dur2 as long: ${dur2.toMilliseconds()}"
    }
    
  }

  static void main( String[] args ) { 
    SecondLockRunner slr = new SecondLockRunner()
    slr.doStuff()
  }
} // end class

So, to review: My question was would DebugClosureLock blow up when it was called since it does not import groovy.time.Duration? And the answer is it did not.

Here is the console output:

about to call dur.toString() 
Is lock 1740808647 held? false
about to try to get the lock
 About to call block. Is lock 1740808647 held? true
30 days, 2 hours, 45 minutes, 32.123 seconds
About to try to unlock
Done with code. Is lock 1740808647 held? false

about to call dur2.toString() 
Is lock 1740808647 held? false
about to try to get the lock
 About to call block. Is lock 1740808647 held? true
3 days, 20 hours, 43 minutes, 31.522 seconds
dur2 as long: 333811522
About to try to unlock
Done with code. Is lock 1740808647 held? false

Paul King shows how to do this on slide 36 of his talk Groovy and Concurrency using metaprogramming:

import java.util.concurrent.locks.ReentrantLock

ReentrantLock.metaClass.withLock = { critical ->
    lock()
    try { critical() }
    finally { unlock() }
}

Then you can use it like this:

ReentranLock lock = new ReentrantLock()
lock.withLock{ someCode() }

You’re welcome.

Image from “Evangeliarium [Evangéliaire dit de Charlemagne ou de Godescalc]”, an 8th century manuscript housed at the Bibliothèque nationale de France. Source gallica.bnf.fr / BnF; image assumed allowed under Fair Use. It also has a Wikipedia page.