This is an issue when using synchronized blocks on objects from the java.util.concurrent package, such as
ReentrantLock, Semaphore, CountDownLatch, CyclicBarrier, BlockingQueue, or atomic
types like AtomicInteger.
Concurrent programming libraries provide their own high-level synchronization mechanisms. These are designed to be more flexible and powerful than the language’s basic object locking mechanism.
When you use the basic locking mechanism on such an object, you are acquiring a lock that is built into the object itself at the language runtime level. However, this built-in lock is completely separate from the object’s own synchronization protocol. The two mechanisms do not interact with each other at all.
For example:
This means that threads using the basic locking mechanism on these objects will not coordinate with threads using the object’s proper API methods. Different threads might think they have exclusive access when they actually don’t, leading to race conditions and data corruption.
This pattern typically indicates a misunderstanding of how concurrent library classes work. The developer likely intended to use the object’s own synchronization mechanism but mistakenly used the basic locking mechanism instead.
In Java, this refers to classes from the java.util.concurrent package (including java.util.concurrent.locks and
java.util.concurrent.atomic) and using the synchronized keyword. Specific examples include using
synchronized(reentrantLock) instead of calling lock()/unlock() on a ReentrantLock, or using
synchronized(semaphore) instead of calling acquire()/release() on a Semaphore, or using
synchronized(atomicInt) instead of calling compareAndSet()/getAndUpdate() on an AtomicInteger.
When language-level locking mechanisms are used on concurrent data structures that implement their own internal synchronization, the code fails to provide the expected thread-safety guarantees:
These issues are particularly dangerous because they often only manifest under specific timing conditions, making them hard to detect during testing.
Replace the synchronized block with the correct API methods for the specific java.util.concurrent class you’re using.
Always use try-finally blocks when acquiring locks to ensure they are released even if an exception occurs.
private final ReentrantLock lock = new ReentrantLock();
public void doWork() {
synchronized (lock) { // Noncompliant
criticalSection();
}
}
private final ReentrantLock lock = new ReentrantLock();
public void doWork() {
lock.lock();
try {
criticalSection();
} finally {
lock.unlock();
}
}