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
ExampleClassobjectsBUT 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.

Top comments (0)