Skip to main content

Command Palette

Search for a command to run...

What I Actually Needed to Know About Java Threading to Pass My IKM Assessment

Updated
4 min read
What I Actually Needed to Know About Java Threading to Pass My IKM Assessment
L

French software engineer building a new life in Japan. My journey here is a big challenge—from learning the language to navigating the tech scene. I use this blog as a space to share what I'm learning, both in tech and in life.

WHY I NEEDED THIS

I had an IKM assessment coming up — one of those technical screeners that stands between you and the next interview round. The test covered Java fundamentals, but I kept hitting threading questions that felt like trick questions. Not "write a producer-consumer" but "what happens when you call run() instead of start()?" The kind of gotchas that separate people who've actually debugged concurrency issues from people who've just read the theory.

WHAT I GOT WRONG FIRST

I thought threading was about knowing the big patterns — executors, thread pools, concurrent collections. That's what every tutorial focuses on. But the assessment questions were different. They tested edge cases and state transitions I'd never thought about.

The run() vs start() question threw me completely. I knew start() was "the right way" but couldn't articulate why calling run() directly would work but defeat the entire purpose. I thought it would error out or do nothing. Turns out it just runs synchronously in your current thread — perfectly valid Java, completely missing the point of threading.

Same with ThreadLocal. I'd seen it in Spring code but never understood what problem it actually solved. I thought it was just "a variable that threads can share" which is backwards — it's specifically for not sharing.

WHAT ACTUALLY WORKED

The breakthrough came when I stopped thinking about threading as "parallel execution" and started thinking about thread states as a state machine. There are exactly six states: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED. Every threading concept maps to transitions between these states.

When you call start(), you transition NEW → RUNNABLE. The JVM creates a new thread and schedules it. When you call run() directly, nothing transitions — the thread object stays in NEW state forever. You're just calling a method. This explains why this code works but isn't concurrent:

Thread thread = new Thread(() -> {
    System.out.println("Running in: " + Thread.currentThread().getName());
});

thread.run();  // Prints "main" — same thread!
System.out.println(thread.getState());  // Still NEW

ThreadLocal clicked when I understood it as per-thread storage with no sharing. Each thread gets its own copy of the variable. The use case isn't "threads communicating" but "threads needing context without passing parameters everywhere." Like storing a user ID for the duration of an HTTP request without threading it through every method call.

StampedLock was the trickiest. I knew ReadWriteLock but StampedLock's "optimistic read" seemed like overengineering until I saw the pattern: try reading without locking, validate afterward, fall back to a real lock only if validation fails. It's faster because the common case (no concurrent writes) costs almost nothing:

long stamp = lock.tryOptimisticRead();  // No lock acquired
int value = data;  // Just read
if (!lock.validate(stamp)) {  // Did someone write during our read?
    stamp = lock.readLock();  // Fall back to real lock
    try {
        value = data;
    } finally {
        lock.unlockRead(stamp);
    }
}

The assessment also tested string interning, which I'd never used. The key insight: intern() puts strings in a shared pool so identical strings point to the same object. Lets you use == instead of equals() for content comparison. Useful when you have thousands of duplicate strings (like "admin", "user" role strings repeated everywhere) and want to save memory.

THE 20% THAT COVERS 80% OF CASES

For threading assessments, know these cold:

Thread states: NEW (created), RUNNABLE (ready/running), BLOCKED (waiting for synchronized lock), WAITING/TIMED_WAITING (waiting on condition), TERMINATED (done). Know which methods cause which transitions.

start() vs run(): start() creates a new thread. run() is just a method call in the current thread. Both "work" but only one is actually concurrent.

ThreadLocal: Each thread gets its own copy. Use for per-request context like user IDs or transaction state. Never share ThreadLocal variables between threads — that defeats the purpose.

Lock semantics: synchronized blocks cause BLOCKED state. Thread.sleep() causes TIMED_WAITING. Object.wait() causes WAITING. StampedLock's optimistic read is faster because it doesn't block readers when there are no writers.

STILL FUZZY ON

I still don't have an intuition for when StampedLock's complexity is worth it versus just using ReadWriteLock. The performance gain matters for high-throughput systems, but how high is "high enough"?

Also uncertain about ThreadLocal memory leaks. I know you're supposed to call remove() in finally blocks to prevent leaks in thread pool scenarios, but I haven't debugged one myself so it's still theoretical knowledge.

The string pool internals are fuzzy too. I know intern() puts strings there and literals are there by default, but I don't know the pool's size limits or when it gets garbage collected. Probably doesn't matter for most code, but assessments love asking about edge cases.


🤖 Generated by AI from Claude's logs.

⚙ Technical

Part 1 of 20

In this series, I gather data on IT concepts and I come back step by step on how I made a project, introduction to a new language/framework, how to deploy on a specific platform, how to use a library.

Up next

⚙ The Angular project's config files

Config files The angular project comes with some config files ending with "rc" for "runcom/run commands". Those files are required to setup environment variables when running commands with those specific tools such as: Husky npm node version manag...

More from this blog

L

LPM Blog

194 posts

日本でのキャリアを切り拓く私の挑戦の記録です。

ここでは、私の就職活動の道のり、技術的な学び、そして異文化で働くことの喜びと苦労を共有していきます。

この挑戦を通じて、ITエンジニアとしての成長を皆さんにお見せできれば幸いです。

What I Actually Needed to Know About Java Threading to Pass My IKM Assessment