Kathy,
The first things that
you should realize is that every object has a "lock." That means that you can lock on any object you create, whether that object be a StringBuffer, a
Thread, a
String, an ArrayList, or whatever. It's also essential to remember that every object has ONLY 1 lock.
Now, let's get back to the problem at hand, shall we?
In this case, we create one instance of a StringBuffer object, referenced originally by the variable sb. We then create
3 InSync objects and pass to each one a reference to the StringBuffer object.
Now, as each InSync method invokes its run method, it runs into the synchronized block. At this point, the thread is going to "request" the lock on whatever object you specify. If that lock is available, the thread acquires it and goes on through the block. If that lock is not available, the thread has to stop and wait until it is available.
So, what happens is we were to lock on "this" rather than on letter.
Well, the first InSync object enters its run method and requests its own lock (this referes to the object, itself). Of course, no other thread is using it, so it acquires the lock and enters the synchronized block.
Now, while that first thread is processing, the next InSync object enters its run method. It runs into the synchronized block and requests its own lock. Of course, no other thread is using it, so it acquires the lock and heads into the synchronized block. Oh no!

That's not what we wanted at all! Now we have 2 threads executing the same synchronized block of code at the same time!
What happened was that we had each thread trying to acquire the lock on a different object (in this case, itself). Since there are 3 InSync objects, there are 3 locks available and all 3 threads can process the synchronized block at the same time. Basically, we've accomplished nothing - we might as well have left the synchronized statement out entirely.
So, what about synchronizing on the variable letter?
Well, the first InSync object enters its run method and hits the synchronized statement. It requests the lock on the StringBuffer referenced by letter and, as no other thread is using it, the InSync object acquires the lock and proceeds to execute the synchronized block.
Now, the second InSync object enters its run method and hits the synchronized block. Likewise, it requests the lock on the StringBuffer referenced by letter - now, however, that lock is already in use by the first InSync object so this thread has to wait. Ahhh, just what we wanted.

Once the first thread is done executing that block and releases the lock, another thread can acquire it and advance.
The "key" (yup, pun intended

) to making this work is that we must have all 3 InSync objects trying to acquire the same lock. As all 3 objects refer to the same StringBuffer object, we can safely use the lock of that object to achieve our goal.
Now, what's this about a "third-party" object? Well, it sounds more complicated than it is. A "third-party" object is something that is somewhat removed from what we're doing. Rather than locking on one of the objects being used (such as one of the threads or the StringBuffer that they're operating on), you might want to create some other object solely for the purpose of creating a lock you can use. As every object has a lock, you could create an object like this:
and safely use it as a lock. Here is an example:
I hoep that helps,
Corey