Below is an example in Bruce Eckel's book Thinking in Java. Don't let the number of lines scare you away, it's actually pretty straightforward. For improved readability, you can just paste it in your IDE if you want to, it's a single file with no dependencies. I've added a few comments here and there for my (and your) convenience.
What the program does is simulate a line of customers queueing in front of a number of teller machines. Each customer is served by a teller for a certain amount of time. Additional customers get in line while the programming is running, and the number of active tellers adjusts itself to the total number of customers (in the TellerManager class).
The compareTo() method in Teller is used by the PriorityQueue in TellerManager and is used for sorting the Tellers in said queue, for example upon calling workingTellers.add(teller). These add methods are called in TellerManager.adjustTellerNumber(), which effectively runs in a dedicated TellerManager thread. You can see that in TellerManager's run() method.
Now, Bruce Eckel has synchronized the Teller.compareTo() method. Since Teller.customersServed is shared between the Teller's thread (look at its run() method) and the TellerManager's thread (which calls workingTellers.add(teller), which calls Teller.compareTo(Teller)), I can see why synchronization is necessary.
The part I'm having a problem with is the implementation of compareTo() on line 106. It makes use of other.customersServed. When TellerManager takes the lock on this, it makes sure that this.customersServed cannot be changed while the lock is held, but in the meanwhile, other.customersServed can still be changed by an other thread, since this thread isn't locking on other. What do you think, am I right?
I'd solve the problem with a private synchronized getCustomersServed() method in Teller, and call this method instead of accessing the field directly.