Java
Concurrency
Multithreading
Best Practices
Programming

Is it OK to use a string as a lock object?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Java, synchronizing on a String is legal, but it is rarely a good design choice. The issue is not whether the syntax works. The issue is that strings are easy to share accidentally, which can make unrelated code block on the same monitor without anyone intending that behavior.

Why String Locks Become Shared Accidentally

Java interns string literals. Equal literals often refer to the same object, which means two classes can end up synchronizing on the exact same monitor even if they were written independently.

java
String a = "LOCK";
String b = "LOCK";
System.out.println(a == b); // usually true

That shared identity is what makes string-based locking risky. The lock object may appear local in the source file while actually being globally shared in practice.

A Hidden Contention Example

This code compiles and looks harmless:

java
1public class ServiceA {
2    private static final String LOCK = "GLOBAL_LOCK";
3
4    public void doWork() {
5        synchronized (LOCK) {
6            // work A
7        }
8    }
9}
10
11public class ServiceB {
12    private static final String LOCK = "GLOBAL_LOCK";
13
14    public void doWork() {
15        synchronized (LOCK) {
16            // work B
17        }
18    }
19}

ServiceA and ServiceB can now block each other because the string literal refers to the same interned object. That kind of coupling is difficult to discover during code review and frustrating to diagnose in production.

Use a Dedicated Private Lock Object Instead

The safest default is to create a private final object whose only purpose is synchronization.

java
1public class Counter {
2    private final Object lock = new Object();
3    private int value;
4
5    public void increment() {
6        synchronized (lock) {
7            value++;
8        }
9    }
10
11    public int getValue() {
12        synchronized (lock) {
13            return value;
14        }
15    }
16}

This approach gives the lock a single owner and prevents accidental interaction with external code.

Keep the Lock Encapsulated

Even a non-string lock becomes dangerous if it is exposed outside the class. Good locking discipline means:

  • make the lock field private
  • make it final
  • never return it from a getter
  • document what state it protects

Lock design is part of encapsulation. If other code can synchronize on your lock, it can stall your internal implementation.

Use ReentrantLock When You Need More Control

Sometimes synchronized is not expressive enough. If you need timeouts, interruption support, or explicit lock management, prefer ReentrantLock over inventing a lock strategy based on strings.

java
1import java.util.concurrent.locks.ReentrantLock;
2
3public class JobRunner {
4    private final ReentrantLock lock = new ReentrantLock();
5
6    public void runJob() {
7        lock.lock();
8        try {
9            // critical section
10        } finally {
11            lock.unlock();
12        }
13    }
14}

This is clearer, more flexible, and easier to reason about than synchronizing on a value-like object.

Avoid intern() as a Lock Registry

Some code uses intern() deliberately to create shared lock keys from strings. While that can work in narrow cases, it creates global coordination through the string pool and makes concurrency behavior much harder to predict.

If you truly need keyed locking inside one JVM, a dedicated lock registry is usually a better design.

java
1import java.util.concurrent.ConcurrentHashMap;
2
3public class LockRegistry {
4    private final ConcurrentHashMap<String, Object> locks = new ConcurrentHashMap<>();
5
6    public Object lockFor(String key) {
7        return locks.computeIfAbsent(key, k -> new Object());
8    }
9}

This still needs lifecycle thought, but at least the sharing is explicit and under your control.

Common Pitfalls

One pitfall is assuming a string constant is private enough to be safe. With interning, its identity may already be shared more broadly than expected.

Another is choosing a string lock because the string was already available. Convenience is a poor reason to define a synchronization boundary.

Developers also forget that sometimes a lock is the wrong abstraction entirely. A concurrent collection, atomic type, or message-passing design may be simpler and safer.

Summary

  • Using a String as a Java lock object is usually a bad practice.
  • String interning can create accidental shared monitors across unrelated code.
  • Prefer a private final lock object with one owner and one purpose.
  • Use ReentrantLock when you need advanced locking behavior.
  • If you need key-based locking, build an explicit registry instead of relying on string interning.

Course illustration
Course illustration

All Rights Reserved.