- All non-final, non-volatile fields (and final references to non-thread-safe state) must be annotated with @GuardedBy(x), where x is usually this, meaning that all access to the field -- reads and writes -- is performed within a synchronized (this) block or synchronized method. [This is probably the hardest rule for people to accept; it's not intuitive that reads must be performed with a lock held. See amplification below.]
- If two fields annotated with @GuardedBy participate in the same class invariant, they must be annotated with the same argument to @GuardedBy. [This is trivially satisfied if you only use @GuardedBy("this").]
- All compound actions with atomic semantics (e.g., check-then-act and read-modify-write sequences) must be performed completely within a synchronized block or method. [This is what many people incorrectly think is the only reason to use synchronized.]
- Do not make calls to code whose synchronization properties you don't know or control from inside a synchronized block or method; prefer open calls (calls made with no locks held). [This is a strategy for deadlock avoidance.]
- Prefer final fields. For collection fields, prefer thread-safe collections. (But don't bother using a thread-safe collection if the field needs to be @GuardedBy for some other reason.)
- Prefer final atomic variables to volatiles. Volatile fields cannot be modified atomically without a synchronized block.
- There need be no special relationship between x and y in "@GuardedBy(x) Type y;" as long as all access to y is made while holding x's lock.
- Don't be too clever: even if your clever reasoning is correct right now, it will be harder in the future for others (and you) to understand and maintain the code that relies on that reasoning.
Brian Goetz suggests that I make this point more strongly: Not only do you need to declare such fields @GuardedBy(lock), you have to acquire lock every time you access the field in any way.