@Documented @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) @GroovyASTTransformationClass("org.codehaus.groovy.transform.SynchronizedASTTransformation") public @interface Synchronized
Method annotation to make a method call synchronized for concurrency handling with some useful baked-in conventions.
@Synchronized
is a safer variant of the synchronized
method modifier.
The annotation can only be used on static and instance methods. It operates similarly to
the synchronized
keyword, but it locks on different objects. When used with
an instance method, the synchronized
keyword locks on this
, but the annotation
locks on a (by default automatically generated) field named $lock
.
If the field does not exist, it is created for you. If you annotate a static method,
the annotation locks on a static field named $LOCK
instead.
If you want, you can create these locks yourself.
The $lock
and $LOCK
fields will not be generated if you create
them yourself. You can also choose to lock on another field, by specifying its name as
parameter to the @Synchronized
annotation. In this usage variant, the lock field
will not be created automatically, and you must explicitly create it yourself.
Rationale: Locking on this
or your own class object can have unfortunate side effects,
as other code not under your control can lock on these objects as well, which can
cause race conditions and other nasty threading-related bugs.
Example usage:
class SynchronizedExample { private final myLock = new Object()which becomes:@
Synchronized static void greet() { println "world" }@
Synchronized int answerToEverything() { return 42 }@
Synchronized("myLock") void foo() { println "bar" } }
class SynchronizedExample { private static final $LOCK = new Object[0] private final $lock = new Object[0] private final myLock = new Object() static void greet() { synchronized($LOCK) { println "world" } } int answerToEverything() { synchronized($lock) { return 42 } } void foo() { synchronized(myLock) { println "bar" } } }Credits: this annotation is inspired by the Project Lombok annotation of the same name. The functionality has been kept similar to ease the learning curve when swapping between these two tools.
Details: If $lock
and/or $LOCK
are auto-generated, the fields are initialized
with an empty Object[]
array, and not just a new Object()
as many snippets using
this pattern tend to use. This is because a new Object
is NOT serializable, but
a 0-size array is. Therefore, using @Synchronized
will not prevent your
object from being serialized.
More examples:
import groovy.transform.Synchronized class Util { private counter = 0 private def list = ['Groovy'] private Object listLock = new Object[0] @Synchronized void workOnCounter() { assert 0 == counter counter++ assert 1 == counter counter -- assert 0 == counter } @Synchronized('listLock') void workOnList() { assert 'Groovy' == list[0] list << 'Grails' assert 2 == list.size() list = list - 'Grails' assert 'Groovy' == list[0] } } def util = new Util() def tc1 = Thread.start { 100.times { util.workOnCounter() sleep 20 util.workOnList() sleep 10 } } def tc2 = Thread.start { 100.times { util.workOnCounter() sleep 10 util.workOnList() sleep 15 } } tc1.join() tc2.join()