Java Questions 1 - 10   «Prev  Next»

Locking Policies and Thread Safety in Java SE 25

Thread safety depends on a consistent policy for shared state. These ten questions examine class locks, instance locks, condition waiting, and compound operations.

  1. Does a static synchronized method block an instance synchronized method?

    Answer: Not merely because both are synchronized. The static method locks its declaring Class object, while the instance method locks its receiver. Since those are different monitors, the two methods can ordinarily execute concurrently.

    It is too strong to say they can never block one another. Their bodies might acquire a common additional lock, wait for each other, or call code that does so. Analyze every lock and dependency used by the operation. If the two methods access the same mutable invariant, their default monitors alone do not protect that shared access.

  2. What happens to a monitor when its owner calls wait?

    Answer: Object.wait requires ownership of the target object's monitor, temporarily releases all acquisitions of that monitor, and waits. It does not release monitors held on other objects. Before the call returns, it reacquires the target monitor and restores the previous reentrant acquisition count.

    Use a while loop around the condition because wakeups can be spurious or another thread can change the condition before the waiter reacquires the monitor. NotifyAll signals waiters but does not release the notifying thread's monitor. Here is a one-message example:

    public class MessageHandoff {
        private String message;
    
        synchronized void publish(String text) {
            if (message != null) throw new IllegalStateException("Already published");
            message = java.util.Objects.requireNonNull(text);
            notifyAll();
        }
    
        synchronized String awaitMessage() throws InterruptedException {
            while (message == null) wait();
            return message;
        }
    
        public static void main(String[] args) throws InterruptedException {
            MessageHandoff handoff = new MessageHandoff();
            Thread producer = new Thread(() -> handoff.publish("Ready"));
            producer.start();
            System.out.println(handoff.awaitMessage());
            producer.join();
        }
    }

    The program prints Ready regardless of whether publication occurs before or after awaitMessage begins. The shared condition, not the timing of the notification, determines whether waiting is necessary. For general producer-consumer queues, prefer an appropriate BlockingQueue to hand-building a queue protocol.

  3. When does shared data require a concurrency policy?

    Answer: When multiple threads access shared mutable state and at least one writes, the design must address conflicting access and visibility. Synchronized is one option; confinement, correctly published immutable objects, volatile fields for suitable simple state, atomic variables, and concurrent collections are others.

    Multiple readers of unchanging, safely published data do not need a new monitor acquisition merely because there are several readers. Conversely, a single writer does not make unsynchronized readers safe. Identify the invariant and required happens-before relationships rather than counting writers alone.


  4. Why synchronize a critical section instead of the entire application?

    Answer: A critical section groups operations that must preserve a shared invariant under a common lock. This prevents conflicting interleavings and provides visibility when the same monitor is subsequently acquired. Unrelated work can continue concurrently.

    Keep the protected region large enough to cover the complete invariant but avoid unnecessary slow I/O, callbacks, or waiting while holding the lock. Splitting a necessary read-modify-write operation into separately synchronized pieces can reintroduce a race. The correct boundary follows the data dependency, not an arbitrary preference for very small or very large methods.

  5. Are static fields safe because they are accessed through static methods?

    Answer: No. Static identifies class-level membership, not thread safety. A plain static method supplies no automatic synchronization. Static mutable data may be shared by many instances and callers, so its accesses need a common policy.

    A static synchronized method can guard that data using the declaring Class monitor, provided all relevant accesses follow that policy. A private static final lock object is another option. Atomic variables suit some independent updates; volatile can publish a simple value but does not make multi-step changes atomic. A static final reference also does not make the referenced object's contents immutable.

  6. Can a static method access instance fields?

    Answer: Yes, through an explicit reference to an instance, such as a parameter. A static method has no implicit this, so it cannot refer to an instance field as though a receiver were automatically available.

    If the static method is synchronized, its automatic lock is still the declaring Class object, not the instance supplied as a parameter. To coordinate with that object's instance synchronized methods, it must use the same instance monitor or call an operation that acquires it. Accessibility and synchronization are separate questions: being allowed to access a field does not establish safe concurrent access.

  7. Why can class-level and instance-level synchronized operations run at once?

    Answer: They acquire different monitors. One thread can own a particular instance's monitor while another owns its Class object's monitor. Similarly, synchronized instance methods on two different objects can run concurrently.

    This independence can be useful when the operations protect separate state. It becomes a problem when they both update the same static collection or shared referenced object while mistakenly assuming that “synchronized” is a global lock. Trace the actual object used as each lock and ensure it matches the ownership of the protected data.

  8. What must be considered when making a class thread-safe?

    Answer: Consider its complete observable behavior: state invariants, all reads and writes, construction and publication, returned references, callbacks, and compound operations. Adding synchronized to one method is insufficient if another exposes the same mutable data without compatible protection.

    Favor immutable state or confinement where feasible. When locking is needed, document the guarding lock and a consistent order for acquiring multiple locks. Avoid letting a partially constructed object escape to another thread. Review returned mutable collections and views: callers can otherwise bypass the class's policy even when its own methods appear correctly synchronized.

  9. Must static state use static synchronized methods and instance state use instance synchronized methods?

    Answer: No. That is a possible convention, not a language requirement. The real rule is that every operation participating in a shared invariant must use compatible coordination. An instance method can explicitly acquire a class-level lock, and a static method can explicitly acquire an instance lock when appropriate.

    One instance monitor per object does not protect a static variable shared by all instances. Conversely, a single class-wide lock can unnecessarily serialize independent per-instance state. Select a policy that provides the required atomicity and visibility without implying that a method's static modifier alone determines correctness.

  10. What does it mean for a class to be thread-safe?

    Answer: Its documented operations behave correctly when used concurrently under the conditions of its contract, preserving required invariants and visibility. Callers should not need additional synchronization for operations the class promises are safe. If an API requires external locking for a particular use, that requirement must be explicit.

    Thread-safe individual operations do not necessarily make an arbitrary sequence atomic. For example, a check followed by an update may need a dedicated compound operation or external coordination. Thread safety also does not by itself promise fairness, low latency, or freedom from every possible application-level deadlock when the class is combined with unrelated locks.

References: Java memory model and synchronization, Object.wait and notification, and Concurrency utilities and memory-consistency guarantees.


SEMrush Software