Showing posts with label Programming. Show all posts
Showing posts with label Programming. Show all posts

Friday, December 12, 2025

Java Synchronization Explained: 4 Hidden Truths Every Developer Misses

 

Introduction: The Most Valuable—and Feared—Concept in Multithreading

In the world of Java multi-threading, one topic is both the most valuable and, for many, the most intimidating: synchronization. It’s a concept that can make even experienced developers panic, yet it's built on principles that are surprisingly simple.

This article will demystify synchronization for good. Forget complex diagrams and dense definitions. We're going to break down the four most critical truths about how it works using a couple of simple, memorable analogies straight from the source: a plate of delicious biryani and a few very selfish dogs.

--------------------------------------------------------------------------------

1. The Core Problem Isn't Code, It's a Plate of Biryani

Before you can understand the solution (synchronized), you have to understand the problem it solves: data inconsistency. And the best way to understand data inconsistency is to picture this scenario.

Imagine a single, large plate of biryani left on the side of the road. One dog sees it and happily approaches, planning its meal. But before it can start, another dog arrives. Then a third, and a fourth.

Their instincts take over. Instead of sharing, they start fighting. Every dog wants the entire plate for itself. They pull the plate in different directions, spilling the food everywhere until the biryani is a useless mess on the ground.

In this story, the plate of biryani is your Java object. The hungry, selfish dogs are the threads competing for access. The spilled, useless mess on the ground is data corruption. This story perfectly illustrates what happens in software: if multiple threads try to operate on the same Java object simultaneously, you can get data inconsistency problems. The object's state becomes corrupted and useless, just like the spilled biryani.

"if multiple dogs are trying to operate on the same Biryani plate simultaneously... then what will happen is a Biryani inconsistency Problem will come by default."

Analysis

This analogy is powerful because it reframes the problem as one of selfish instinct. The source describes the dog as "the most selfish animal." Just as each dog selfishly wants the entire plate, each thread attempts to monopolize the object, leading to chaos and ultimately rendering the resource useless. It transforms an abstract software problem into a tangible story you can immediately visualize and understand.

2. The Lock Belongs to the Object, Not the Method

One of the biggest points of confusion for developers is understanding what the synchronized keyword actually does. The magic lies in a concept called a "lock," and every single object in Java has one.

Here’s the surprising truth: a thread doesn't lock a method; it acquires the lock of the object to execute a synchronized method.

Let's use the source's example to make this crystal clear. Imagine you have an object X with three methods:

  • **M1** (synchronized)
  • **M2** (synchronized)
  • **M3** (non-synchronized)

Now, consider this sequence of events:

  1. Thread T1 wants to run method M1. It successfully acquires the lock for object X and starts executing M1.
  2. While T1 is still running, Thread T2 comes along and also tries to run M1. Because T1 holds the lock for object X, T2 must wait. No surprise there.
  3. Here’s the crucial part: Thread T3 arrives and tries to run the different synchronized method, M2. T3 also has to wait. Why? Because to run any synchronized method on object X, a thread needs the object's single, unique lock, which T1 is still holding.
  4. Finally, Thread T4 shows up and wants to run the non-synchronized method M3. T4 can execute it immediately without waiting, because non-synchronized methods don't require the object's lock at all.

Analysis

This concept is often counter-intuitive. Many developers initially assume that synchronization happens at the method level. Understanding that the lock is tied to the object is the key to grasping that synchronized is designed to protect an object's entire state from concurrent modification, not just to control access to individual methods.

3. Synchronization's Greatest Strength is Also Its Greatest Weakness

The synchronized keyword has a powerful duality that can catch developers off guard.

Its greatest strength is obvious: it is a highly effective tool for resolving data inconsistency problems. By forcing threads to access a shared resource one by one, it prevents the "spilled biryani" scenario and ensures data integrity.

However, its greatest weakness is the flip side of that same coin: it can severely damage application performance.

The reason is simple. When one thread holds a lock, all other threads that need that same lock are forced to wait. This "increases waiting time of the threads and creates performance problems." If you have thousands of threads waiting in line, your application can grind to a halt. The performance impact is so significant that the source offers a stark warning:

"if there is no specific requirement, it's never recommended to use the synchronized keyword. The worst keyword in Java is synchronized only."

Analysis

This trade-off is central to concurrent programming. synchronized is a powerful tool, not a "fancy keyword" to be sprinkled throughout your code for style, as the source wisely warns. It is a powerful but costly tool. Every time you use it, you must consciously weigh the critical need for data integrity against the potential performance bottleneck you are creating.

4. Every Object Has Two Areas (And Only One Needs a Guard)

To use synchronization effectively without crippling your application's performance, it helps to adopt a powerful mental model: every object can be conceptually divided into two distinct areas.

1. The Synchronized Area

This is the part of your object where operations that change the object's state happen. Think of methods like update, add, remove, or delete. Because these actions modify the object, they must be protected to prevent data inconsistency. This area is guarded by the object's lock, and only one thread is allowed inside at a time.

2. The Non-Synchronized Area

This is the part of your object for operations that do not change the state. The most common example is a read-only operation. Since merely reading data doesn't risk corrupting it, any number of threads can safely access this area simultaneously without causing problems.

The source provides a perfect, practical example from a Reservation System:

  • check availability: This is a read-only operation. It doesn't change the number of available seats. Therefore, it should be non-synchronized to allow many users to check availability at once.
  • book ticket: This is an update operation. It changes the system's state by reducing the number of available seats. Therefore, it must be synchronized to ensure only one booking happens at a time.

Analysis

This mental model empowers developers to make smarter, more granular decisions. Instead of locking down entire objects unnecessarily, you can apply synchronization surgically, protecting only the critical sections of your code that modify state. This allows you to achieve thread safety while minimizing the performance penalty.

--------------------------------------------------------------------------------

Conclusion: A Tool, Not a Golden Hammer

Synchronization is an essential concept for writing robust, multi-threaded Java applications. It is the primary tool for preventing the data corruption that occurs when multiple threads clash over a shared resource.

However, it is not a "golden hammer" to be applied to every problem. Understanding its core truths—that it solves resource contention (the biryani problem), that its lock belongs to the object, that it carries a heavy performance cost, and that you only need to protect the parts of your object that change—is what separates a novice from an expert.

With these principles in mind, ask yourself this question: looking at your own code, where might you be paying the performance price for synchronization when a simple read operation would do?

Thursday, December 11, 2025

Java Thread Priorities Explained: 5 Hidden Truths Every Developer Must Know

 

Most Java developers are familiar with the concept of thread priorities. We learn early on that we can set a value from 1 to 10 to influence which thread gets the CPU's attention. But this seemingly simple concept has surprising nuances that can easily trip up even experienced programmers. The textbook definitions, I've found, often don't tell the whole story.

This article uncovers the most counter-intuitive and impactful truths about how thread priorities really work in Java. We'll move beyond the basics to explore the secrets that can make or break your multi-threaded applications.

1. It's a Priority, Not a Rank: Why 10 is More Important Than 1

A common point of confusion is whether a lower number signifies a higher priority, like a first-place rank in a competition. We are culturally conditioned to think "number one" means the best, but in Java, the opposite is true: 1 represents the minimum priority (Thread.MIN_PRIORITY) and 10 represents the maximum priority (Thread.MAX_PRIORITY).

This reminds me of a story I heard about two illiterate villagers discussing their children's exam results. The first father was distraught, complaining that after spending a fortune on tuition, his son got the "worst rank"—2,500. The second father, beaming with pride, boasted that his obedient son was far better, having achieved the "best performance" with a rank of 148,000! They completely misunderstood that in academic ranks, a lower number is better.

When it comes to Java threads, we must avoid their mistake.

"most of the people are going to feel like this only these are not ranks okay... one is mean priority 10 is Max priority"

Understanding this fundamental rule—that higher numbers mean higher priority—is the crucial first step before we can explore more complex behaviors.

2. The Inheritance Secret: The Default Priority Isn't Always 5

Here's the one that trips up developers on code reviews more than any other: the myth of the universal default priority. Many developers assume that any new thread automatically receives a default priority of 5, or Thread.NORMAL_PRIORITY.

The truth is more nuanced. Only the main thread has a default priority of 5.

All other threads, by default, follow a rule of priority inheritance. A new "child" thread inherits the priority of its "parent" thread—the thread that created it. For example, if you change the main thread's priority to 7 and then create a new thread, that new child will start life with a priority of 7, not 5.

"the default priority only for the main thread is five but for all remaining threats default priority will be inherited from parent to child"

This is a vital secret to know, as assuming every thread starts at NORMAL_PRIORITY can lead to completely wrong assumptions about your application's execution flow.

3. The Scheduler's Gamble: The Truth About Priority Ties

So, a higher-priority thread gets the CPU's attention first, much like a Prime Minister's motorcade gets waved through a traffic signal before anyone else. But what happens when two threads with the exact same priority arrive at the intersection?

When threads of equal priority are competing, there is no guaranteed outcome. The execution order becomes unpredictable and is left entirely to the thread scheduler's implementation. The scheduler might use a round-robin algorithm, giving each a turn, or it might be first-come-first-served. The key is that you, the programmer, cannot and should not rely on a specific behavior.

"if two threats having same priority then... we can't expect exact execution order it depends on thread scheduler"

This serves as an important lesson in the limits of our control. Priorities can influence the scheduler, but they don't provide absolute command over thread execution.

4. The Runtime Ambush: Setting an Invalid Priority

You can change a thread's priority at any time using the setPriority(int p) method. But what happens if you accidentally provide a value outside the legal 1-10 range? The compiler won't warn you. The problem will only surface at runtime, waiting to ambush your application when it's least expected.

When the setPriority() method is called with an invalid value, it throws an IllegalArgumentException. This isn't a benign error; an unhandled IllegalArgumentException can crash a critical thread, leading to data corruption or a service outage.

// This is a valid priority within the 1-10 range.
t.setPriority(7);

// This value is outside the 1-10 range and will cause a runtime error.
t.setPriority(17); // Throws IllegalArgumentException

This is an important "gotcha" to be aware of. Because it's a runtime exception, this bug might go unnoticed during development and only appear in production, making it crucial to validate priority values or catch the exception.

5. The Ultimate Betrayal: Your Operating System Might Just Ignore You

This is the final, most impactful real-world truth: even if you implement thread priorities perfectly in your Java code, they might not work as you expect. I remember the first time I saw this in action. I wrote a simple demo where a high-priority thread (10) should have always printed its output before a low-priority thread (5). I ran it, and the output was completely mixed. My expected output wasn't appearing. "Why is this problem coming?" I wondered.

The reason is that the Java Virtual Machine (JVM) relies on the underlying operating system (OS) for thread management. If the OS doesn't provide robust support for thread priorities, the JVM's instructions are effectively ignored. My demo, running on Windows 7, was a perfect example of this platform-dependent behavior.

"some platforms won't Provide support some platforms won't Provide proper support for thread priorities"

This is a critical warning. You should never rely on thread priorities to control critical application logic or to ensure correctness. They are a hint to the scheduler, not a contract.

Conclusion: A Tool, Not a Guarantee

Thread priorities in Java are a nuanced tool for influencing the thread scheduler, not a strict set of rules that guarantee execution order. As we've seen, their behavior is shaped by inheritance, scheduler implementations, and most critically, the capabilities of the underlying operating system. Relying on them for program correctness is a recipe for unpredictable, platform-dependent bugs.

Knowing that priorities can be so unpredictable, what other concurrency control mechanisms should be the primary tools in a robust multi-threaded application?

Java Threading Mistakes Developers Must Avoid: Runnable vs Thread, start() vs run()


Java multithreading is one of the most powerful features of the language, but it can also be a significant source of bugs and design issues. Even experienced developers can fall into common traps, especially when creating and starting a thread. Many seemingly simple methods can have hidden problems that lead to unexpected behavior or inefficient code.


This article reveals surprising truths about Java threads. By examining the Runnable interface and its connection to the Thread class, we’ll highlight important details that can impact your concurrent applications.


1. The "Best" Way to Create a Thread Isn't the Easiest


There are two main ways to define what a thread will do in Java: extending the Thread class or implementing the Runnable interface. At first, extending Thread seems more straightforward. You create one class, override its run() method, and you’re done. However, implementing Runnable is the recommended approach for a key reason tied to basic object-oriented design principles.


When your class extends Thread, it uses its single inheritance option. Since Java does not allow multiple class inheritance, your class becomes linked to the Thread hierarchy and cannot extend any other class. This limitation makes you forfeit an important inheritance opportunity.


On the other hand, implementing the Runnable interface keeps your class free to extend another class. This method aligns with the essential software design idea of "Composition over Inheritance." Your class defines a task (Runnable) that a worker (Thread) executes, resulting in a more flexible and decoupled design. For this reason, experts suggest that implementing Runnable is the better strategy.


2. The Critical Difference Between .start() and .run()


One of the most common and dangerous misunderstandings for developers new to concurrency is the difference between a thread’s .start() and .run() methods. Calling the wrong one does not produce an error, but it completely changes how the program behaves. The difference is crucial: one starts concurrent execution; the other is just a regular method call.


Calling t.start()


This is the correct way to start a new thread. It tells the Java Virtual Machine (JVM) to allocate resources for a new thread of execution and schedules it to run. This new thread then calls the run() method.


* On a Thread with a Runnable target: A new thread is created to run the run() method from the provided Runnable object.

* On a Thread with no Runnable target: A new thread is created that runs the Thread class's own run() method. Since this method is empty, nothing happens.


Calling t.run()


This does not start a new thread. It simply runs the code in the run() method on the current thread, just like any other normal method call. The program remains single-threaded at that point, and the entire run() method must finish before the next line of code can run.


* On a Thread with a Runnable target: No new thread is started. Instead, it runs the run() method from the Runnable object on the current thread, acting as a standard method call.

* On a Thread with no Runnable target: No new thread is started. The Thread class's empty run() method runs on the current thread, just like any normal method call.


Confusing .run() with .start() is a classic mistake that causes a multithreaded application to behave like a single-threaded one.


3. A Runnable Is a Task, Not a Worker


It’s helpful to think of a Runnable object as a task—a job to be done. The Thread object is the worker that actually executes that task. A Runnable does not have execution capability; it only contains the code that defines the job.


This separation of roles leads to a surprising result if you try to call .start() directly on a Runnable object. It simply won’t work. The code will fail to compile with a clear error message.


This compiler error reinforces the key idea. The Runnable only defines the run() method, which contains the job logic. It has no knowledge of thread scheduling or execution. As the source material logically concludes, if the Runnable object had starting capability, there would be no need for the Thread object.


This distinction is important: Runnable is the "what," and Thread is the "how" and "when." This design also allows Runnable tasks to be passed to more advanced execution tools, such as an ExecutorService, which manages thread pools for better performance and resource handling.


4. Every Thread Has an Identity You Can Change


Every thread running in the JVM has a name. This can be a default name assigned by the JVM (like "main" for the main thread or "Thread-0" for the first child thread) or a custom name you choose. This is more than just a label; it's a valuable debugging tool.


You can work with a thread's name using two methods: thread.getName() and thread.setName(). As the source material states:


Every thread in Java has a name; it may be a default name generated by the JVM or one specifically provided by the programmer.


A great example from the source material shows the practical value of this.


1. First, calling Thread.currentThread().getName() from the main method returns "main."

2. Next, the programmer changes the name using Thread.currentThread().setName("Pawan Kalyan").

3. Finally, to confirm the change worked, an ArithmeticException is triggered by dividing by zero. This clever trick prompts the JVM to print a stack trace.


The resulting stack trace provides clear proof, showing that the main thread's identity changed. Instead of the default name, the custom name appears directly in the exception message.


Giving your threads meaningful names can turn a confusing stack trace from a complex concurrent system into a clear pointer to the source of a problem.


Conclusion


Mastering Java's multithreading capabilities involves going beyond the obvious syntax and understanding the subtle mechanics behind it. The preference for Runnable over extending Thread, the critical difference between .start() and .run(), the roles of task versus worker, and the usefulness of thread naming are all details that differentiate strong concurrent code from fragile, unpredictable applications.


Now that you’ve explored the deeper aspects of creating threads, what other fundamental Java concepts might deserve another look?

Thursday, December 4, 2025

Beyond extends: 5 Surprising Truths About Java Inheritance

 

Inheritance is one of a Java developer's first lessons. At its core, it's simple: a Child class can inherit fields and methods from a Parent class. This "is-a" relationship is fundamental to object-oriented programming, allowing us to build hierarchies and reuse code. We learn the extends keyword, and for many, the lesson stops there.

But beneath this straightforward definition lies a system of carefully considered rules, counter-intuitive behaviors, and design decisions with profound implications. These rules aren't arbitrary; they exist to ensure type safety, prevent logical contradictions, and enable powerful features like polymorphism.

This article moves beyond the textbook definition to explore some of the most impactful and non-obvious truths about how inheritance really works in Java. Understanding the "why" behind these rules will change how you design and write your code.

A Parent Reference Can't See a Child's Unique Methods—Even When It Holds a Child Object

One of the most flexible features of inheritance is the ability to use a parent class reference to hold a child class object. For example, Parent p1 = new Child(); is perfectly valid Java code. It seems intuitive that since p1 actually holds a Child object, you should be able to call any of the Child's methods. This is not the case.

The rule is that even when a parent reference holds a child object, you can only call methods defined in the parent class using that reference. The instructor in our source material calls this a "very dangerous point" precisely because it feels counter-intuitive. Trying to call a method that only exists in the child class will result in a compiler error, likely stating something like cannot find symbol: method M2() location: class Parent.

Consider this code example:

class Parent {
    public void M1() {
        System.out.println("parent");
    }
}

class Child extends Parent {
    public void M2() {
        System.out.println("child");
    }
}

class Test {
    public static void main(String[] args) {
        Parent p1 = new Child();
        p1.M1(); // Valid
        p1.M2(); // Invalid - Compiler Error!
    }
}

The call to p1.M1() is valid because M1() is defined in the Parent class. However, the call to p1.M2() is invalid because the compiler only sees p1 as a Parent type, and the Parent class has no M2() method.

parent reference can be used to hold child object but by using that reference we can call only the methods available in the parent class we can't call child specific methods

This principle is a cornerstone of Java's compile-time type checking. While it might seem restrictive, it is the key to enabling polymorphism, a powerful concept that allows a single interface to represent different underlying forms.

Code Reusability Isn't Just a Buzzword; It's the Blueprint of the Entire Java API

The primary advantage of inheritance is code reusability. By placing common methods in a parent class, multiple child classes can use them without having to rewrite the same logic, solving the problem of code redundancy. The impact is not trivial; it can dramatically reduce development time and complexity.

Imagine designing a loan module without inheritance. You might have separate classes for different loan types, creating significant code redundancy:

  • VehicleLoan (300 methods)
  • HomeLoan (300 methods)
  • PersonalLoan (300 methods)

This approach requires writing a total of 900 methods and could take 90 hours of development time.

Now, consider the same module built with inheritance. You realize that 250 methods are common to all loan types. By placing these in a parent Loan class, the structure changes:

  • Loan (250 common methods)
  • VehicleLoan extends Loan (50 specific methods)
  • HomeLoan extends Loan (50 specific methods)
  • PersonalLoan extends Loan (50 specific methods)

This new approach requires writing only 400 methods and reduces development time to 40 hours. You write the common code once and reuse it everywhere.

This exact principle is the blueprint for the entire Java API. The Object class is the ultimate parent, or root, for all Java classes. It contains 11 common methods fundamental to any object. Because every class in Java is a child of Object, these 11 methods are automatically available to over 5,000 core Java classes without ever being rewritten.

This design pattern appears in other foundational areas of the API as well. The Throwable class, for instance, acts as the root for the entire Java exception hierarchy. The most common methods required by any exception or error are defined in Throwable, ensuring that this core functionality is available to every class in the hierarchy without being rewritten. This design provides a common, reliable foundation for every object and every throwable in the Java ecosystem.

Java Says "No" to Multiple Inheritance for a Very Good Reason: Ambiguity

A Java class can extend only one other class. An expression like Class A extends B, C is invalid and will cause a compiler error. Java's designers deliberately chose to forbid this kind of "multiple inheritance" for classes to prevent a critical issue: the ambiguity problem.

To understand this, imagine if a class could have two parents.

  • Class C extends both Parent P1 and Parent P2.
  • Parent P1 has a method called M1().
  • Parent P2 also has a method with the exact same name, M1().

Now, if you create an object of Class C and call the M1() method, which version should be executed? The one from P1 or the one from P2? The compiler would have no way to resolve this conflict. This confusion is known as the ambiguity problem. To avoid it entirely, Java simply does not allow a class to extend more than one parent.

...But Interfaces Get a Special Pass to Break the Rule

In surprising contrast to classes, an interface can extend multiple other interfaces. This means interface C extends A, B is perfectly valid. This seems to contradict the rule against multiple inheritance, but it's allowed because the ambiguity problem doesn't apply to interfaces.

The reason is simple: interfaces contain only method declarations (signatures), not implementations (code bodies).

Even if a child interface inherits two M1() method declarations from two different parent interfaces, there is no conflict. The conflict only arises when there are two different implementations to choose from. In the case of interfaces, a single implementation class is responsible for providing the one, unique code body for that method.

even though multiple method declarations are available but implementation is unique and hence there is no chance of ambiguity problem in interfaces

Because the final implementation is singular, there is no ambiguity for the compiler to resolve, making multiple inheritance safe for interfaces.

Here's an expert secret, though: strictly speaking, what interfaces provide isn't true inheritance in the same sense as classes. Because only method signatures are passed down—not implementations—no actual code is being reused. However, because an interface can extend multiple other interfaces, it is commonly and practically referred to as a form of multiple inheritance.

Java Protects You From Creating "Inheritance Loops"

Java's inheritance model requires a clear, hierarchical, and acyclic structure. To enforce this, Java forbids "cyclic inheritance," which would create a logical impossibility.

The following scenarios are illegal and will produce a compiler error for cyclic inheritance involving A:

  1. A class extending itself:
  2. Two classes extending each other:

This is forbidden because, as the source material notes, it's a "useless" concept. If Class A needs all of B's methods and Class B needs all of A's methods, it implies that all the methods should simply be defined within a single, unified class. The prohibition of cyclic inheritance protects the logical integrity of the class hierarchy.

Conclusion: Beyond the Textbook Definition

Inheritance in Java is more than just a simple parent-child relationship for sharing code. It is a robust system governed by a set of well-defined rules that promote code reuse, guarantee type safety, and maintain logical consistency across the entire language. By understanding the reasons behind limitations like the ban on multiple inheritance for classes or the prevention of inheritance loops, we gain a deeper appreciation for the design of the language.

Now that you see the 'why' behind these rules, how does it change the way you think about designing your own class hierarchies?

Featured Post

How LLMs Really Work: The Power of Predicting One Word at a Time

  1.0 Introduction: The Intelligence Illusion The most profound misconception about modern AI is that it understands . While models like Cha...

Popular Posts