Thursday, May 14, 2026

Class Level Locking in Java - inspired by Android's Asynctask implementation...




After many months, I have opened my Java workspace and saw my implementation of Class Level locking, which was inspired by Android's AsyncTask.

Here is the source code...

package com.somitsolutions.java.training.classlevellockingwithstaticnestedclass;



public class ExampleClass {

public ExampleClass(){

}

public static InnerNestedClass objInnerNestedClass = new InnerNestedClass();

static class InnerNestedClass{

public synchronized void testMethod(){

try {

for (int i = 0; i<10; i++){

System.out.println ("The testMethod for " + Thread.currentThread().getName() + " Object");

Thread.sleep(2000);

}

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}




package com.somitsolutions.java.training.classlevellockingwithstaticnestedclass;


public class R implements Runnable{

final ExampleClass exampleClassObject = new ExampleClass();

//final ExampleClass exampleClassObject2 = new ExampleClass();

public void run(){

exampleClassObject.objInnerNestedClass.testMethod();

}

}




package com.somitsolutions.java.training.classlevellockingwithstaticnestedclass;


public class Main {


public static void main(String[] args) {

// TODO Auto-generated method stub

R r1 = new R();

R r2 = new R();

Thread t1 = new Thread(r1);

Thread t2 = new Thread(r2);

t1.setName("Thread 1");

t2.setName("Thread 2");

t1.start();

t2.start();

}

}


1. What Exactly Is Happening Here?

We have:

public static InnerNestedClass objInnerNestedClass = new InnerNestedClass();

This line is the key.

Because the object is declared static, there is only one instance of InnerNestedClass for the entire JVM class ExampleClass.

No matter how many ExampleClass objects you create:

new ExampleClass();
new ExampleClass();
new ExampleClass();

they all share:

ExampleClass.objInnerNestedClass

There is exactly ONE monitor lock associated with that object.


2. Understanding the Synchronization

Inside the nested class:

public synchronized void testMethod()

This means:

synchronized(this)

So the lock is acquired on:

objInnerNestedClass

Since there is only ONE static instance:

public static InnerNestedClass objInnerNestedClass

all threads compete for the SAME monitor lock.


3. Flow of Execution

Step-by-step

You create:

R r1 = new R();
R r2 = new R();

Each R object creates its own:

final ExampleClass exampleClassObject = new ExampleClass();

So now you have:

  • Two different ExampleClass objects

  • BUT both point to the SAME static object

Conceptually:

ExampleClass Object A
        |
        ---> static objInnerNestedClass ----> [ONE OBJECT]

ExampleClass Object B
        |
        ---> static objInnerNestedClass ----> [SAME OBJECT]

4. What Happens When Threads Run?

Thread 1:

exampleClassObject.objInnerNestedClass.testMethod();

Thread 2:

exampleClassObject.objInnerNestedClass.testMethod();

Both are calling:

testMethod()

on the SAME shared object.

Since the method is synchronized:

public synchronized void testMethod()

only ONE thread can enter at a time.


5. Runtime Behavior

Suppose:

  • Thread 1 enters first

  • Thread 2 tries to enter

Then:

Thread 1 acquires monitor lock
Thread 2 BLOCKS

Thread 2 waits until:

Thread 1 exits testMethod()

Only then:

Thread 2 acquires lock

So output becomes SERIALIZED:

Thread 1 messages...
Thread 1 messages...
Thread 1 messages...

THEN

Thread 2 messages...
Thread 2 messages...

instead of interleaving.


6. Why This Is Called "Class-Level Locking"

Strictly speaking, true class-level locking is:

synchronized(ExampleClass.class)

or

public static synchronized void method()

which locks on the Class object itself.

But my example achieves a VERY SIMILAR EFFECT using:

static shared object + synchronized instance method

So effectively:

One shared lock for entire class

Hence, behaviorally, it becomes "class-wide serialization."


7. Why Static Matters

Without static:

public InnerNestedClass objInnerNestedClass

each ExampleClass object would get its OWN nested object.

Then:

Thread 1 -> Lock A
Thread 2 -> Lock B

No contention.

Both threads would run simultaneously.

So static is the entire reason serialization occurs.


8. Relationship to Android AsyncTask

In old Android implementations of Android AsyncTask, Google implemented task serialization using a very similar design.

Internally, AsyncTask had something conceptually similar to:

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        ...
    }
}

and:

private static final SerialExecutor sDefaultExecutor
        = new SerialExecutor();

Notice the same pattern:


Shared Static Executor

private static final SerialExecutor sDefaultExecutor

ONE executor object for all AsyncTasks.


Synchronized Method

public synchronized void execute(...)

Only one thread could manipulate scheduling state at a time.


Result

Even if you created:

new MyAsyncTask().execute();
new MyAsyncTask().execute();
new MyAsyncTask().execute();

They were serialized through the SAME shared executor.

So tasks are executed one after another.

This was done to avoid:

  • race conditions

  • thread explosion

  • UI instability

  • uncontrolled parallelism

especially on low-memory mobile devices.


9. Why Android Did This

Early Android phones had:

  • very limited RAM

  • single-core CPUs

  • weak scheduling capabilities

If developers accidentally launched many background tasks simultaneously:

UI freezes
Battery drain
ANRs
Memory pressure

could happen.

So Android engineers intentionally serialized AsyncTasks.

Later Android versions introduced:

executeOnExecutor(THREAD_POOL_EXECUTOR)

to allow controlled parallelism.


10. Deeper JVM Insight

Every Java object has an associated:

Monitor Lock

When a synchronized instance method is called:

public synchronized void method()

JVM internally does roughly:

monitorenter(this)
...
monitorexit(this)

Since all threads share the SAME static object:

ONE monitor

becomes the synchronization bottleneck.


11. Why Studying Open Source Code Is Important

This is the most important part.

Reading open-source frameworks teaches things that textbooks usually cannot.

For example, from AsyncTask we learn:

  • real-world concurrency design

  • serialization strategies

  • thread scheduling

  • producer-consumer patterns

  • executor frameworks

  • synchronization tradeoffs


Theory vs Reality

A textbook may say:

"synchronized prevents race conditions"

But Android source code shows:

WHY engineers used synchronization
WHERE they used it
WHAT problem they were solving
WHAT tradeoffs they accepted

That is real engineering knowledge.


12. Why Great Engineers Read Source Code

Engineers who study frameworks like:

  • OpenJDK

  • Android

  • Linux kernel

  • OpenFOAM

  • FreeCAD

develop:

  • architectural thinking

  • systems intuition

  • debugging maturity

  • performance awareness

  • concurrency understanding

far beyond ordinary programming.


13. What You Are Actually Learning Here

My small example contains concepts from:

  • JVM monitor implementation

  • object lifetime

  • static memory model

  • synchronization semantics

  • concurrent scheduling

  • executor design

  • Android framework architecture

This is exactly why studying framework source code is powerful.

You stop seeing programming as:

"writing syntax"

and begin seeing it as:

"designing systems"

That transition is what separates an average coder from a strong software engineer.

No comments: