Synchronization vs Lock Synchronization vs Lock multithreading multithreading

Synchronization vs Lock


If you're simply locking an object, I'd prefer to use synchronized

Example:

Lock.acquire();doSomethingNifty(); // Throws a NPE!Lock.release(); // Oh noes, we never release the lock!

You have to explicitly do try{} finally{} everywhere.

Whereas with synchronized, it's super clear and impossible to get wrong:

synchronized(myObject) {    doSomethingNifty();}

That said, Locks may be more useful for more complicated things where you can't acquire and release in such a clean manner. I would honestly prefer to avoid using bare Locks in the first place, and just go with a more sophisticated concurrency control such as a CyclicBarrier or a LinkedBlockingQueue, if they meet your needs.

I've never had a reason to use wait() or notify() but there may be some good ones.


I am wondering which one of these is better in practice and why?

I've found that Lock and Condition (and other new concurrent classes) are just more tools for the toolbox. I could do most everything I needed with my old claw hammer (the synchronized keyword), but it was awkward to use in some situations. Several of those awkward situations became much simpler once I added more tools to my toolbox: a rubber mallet, a ball-peen hammer, a prybar, and some nail punches. However, my old claw hammer still sees its share of use.

I don't think one is really "better" than the other, but rather each is a better fit for different problems. In a nutshell, the simple model and scope-oriented nature of synchronized helps protect me from bugs in my code, but those same advantages are sometimes hindrances in more complex scenarios. Its these more complex scenarios that the concurrent package was created to help address. But using this higher level constructs requires more explicit and careful management in the code.

===

I think the JavaDoc does a good job of describing the distinction between Lock and synchronized (the emphasis is mine):

Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements. They allow more flexible structuring, may have quite different properties, and may support multiple associated Condition objects.

...

The use of synchronized methods or statements provides access to the implicit monitor lock associated with every object, but forces all lock acquisition and release to occur in a block-structured way: when multiple locks are acquired they must be released in the opposite order, and all locks must be released in the same lexical scope in which they were acquired.

While the scoping mechanism for synchronized methods and statements makes it much easier to program with monitor locks, and helps avoid many common programming errors involving locks, there are occasions where you need to work with locks in a more flexible way. For example, **some algorithms* for traversing concurrently accessed data structures require the use of "hand-over-hand" or "chain locking": you acquire the lock of node A, then node B, then release A and acquire C, then release B and acquire D and so on. Implementations of the Lock interface enable the use of such techniques by allowing a lock to be acquired and released in different scopes, and allowing multiple locks to be acquired and released in any order.

With this increased flexibility comes additional responsibility. The absence of block-structured locking removes the automatic release of locks that occurs with synchronized methods and statements. In most cases, the following idiom should be used:

...

When locking and unlocking occur in different scopes, care must be taken to ensure that all code that is executed while the lock is held is protected by try-finally or try-catch to ensure that the lock is released when necessary.

Lock implementations provide additional functionality over the use of synchronized methods and statements by providing a non-blocking attempt to acquire a lock (tryLock()), an attempt to acquire the lock that can be interrupted (lockInterruptibly(), and an attempt to acquire the lock that can timeout (tryLock(long, TimeUnit)).

...


You can achieve everything the utilities in java.util.concurrent do with the low-level primitives like synchronized, volatile, or wait / notify

However, concurrency is tricky, and most people get at least some parts of it wrong, making their code either incorrect or inefficient (or both).

The concurrent API provides a higher-level approach, which is easier (and as such safer) to use. In a nutshell, you should not need to use synchronized, volatile, wait, notify directly anymore.

The Lock class itself is on the lower-level side of this toolbox, you may not even need to use that directly either (you can use Queues and Semaphore and stuff, etc, most of the time).