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.