1) In synchronized block, thread might end up waiting indefinitely for the lock. But in case of lock, we can use Lock.tryLock(long timeout, TimeUnit timeUnit) to make sure thread waits for specific time only for lock.
2) Synchronization blocks or methods can cover only one method. A Lock can have its calls to lock() and unlock() in separate methods.
3) synchronized keyword doesn’t provide fairness whereas we can set fairness to true while creating ReentrantLock object so that longest waiting thread gets the lock first.
4) We can create different conditions for Lock and different thread can await() for different conditions.
5) Synchronization code is much cleaner and easy to maintain whereas with Lock we are forced to have try-finally block to make sure Lock is released even if some exception is thrown between lock() and unlock() method calls.