DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Building Your Own Automatic Garbage Collector: A Guide for Developers
  • JVM Memory Architecture and GC Algorithm Basics
  • Java Object Size: Estimating, Measuring, and Verifying via Profiling
  • Java Thread Synchronization and Concurrency Part 1

Trending

  • Analyzing “java.lang.OutOfMemoryError: Failed to create a thread” Error
  • Key Considerations in Cross-Model Migration
  • Scaling in Practice: Caching and Rate-Limiting With Redis and Next.js
  • How to Format Articles for DZone
  1. DZone
  2. Coding
  3. Java
  4. Multithreading and the Java Memory Model

Multithreading and the Java Memory Model

By 
Christopher Wong user avatar
Christopher Wong
·
Oct. 05, 09 · Interview
Likes (0)
Comment
Save
Tweet
Share
46.5K Views

Join the DZone community and get the full member experience.

Join For Free

At the New England Software Symposium, I attended Brian Goetz's session called "The Java Memory Model". When I saw the phrase "memory model" in the title I thought it would be about garbage collection, memory allocation and memory types. Instead, it is really about multithreading. The difference is that this presentation focuses on visibility, not locking or atomicity. This is my attempt to summarize his talk.

The importance of visibility

Visibility here refers to the memory that an executing thread can see once it is written. The big gotcha is that when thread A writes something before thread B reads it, it does not mean thread B will read the correct value. You could ensure that threads A and B are ordered with locking but you can still be in deep doo doo because the memory is not written and read in order, or is read in a partially written state.

A big part of this peril comes from the layered memory architecture of modern hardware: multi-CPU, multi-core CPUs, multi-level caches on and off chip etc. Instructions could be executed in parallel or out of order. The memory being written may not even be in RAM at all: it could be on a remote core's register. But the danger could also come from old-fashioned compiler optimizations. One of Brian's examples is the following loop which depends on another thread to set the boolean field asleep:

while (!asleep) ++sheep;

The compiler may notice that asleep is loop-invariant and optimize its evaluation out of the loop

if (!asleep) while (true) ++sleep; 

The result is an infinite loop. The fix in this case is to use a volatile variable.

The Java Memory Model

A memory model describes when one thread's actions are guaranteed to be visible to another. The Java memory model (JMM) is quite an achievement: previously, memory models were specific to each processor architecture. A cross-platform memory model takes portability well beyond being able to compile the same source code: you really can run it anywhere. It took until Java 5 (JSR 133) to get the JMM right.

The JMM defines a partial ordering on program actions (read/write, lock/unlock, start/join threads) called happens-before. Basically, if action X happens-before Y, then X's results are visible to Y. Within a thread, the order is basically the program order. It's straightforward. But between threads, if you don't use synchronized or volatile, there are no visibility guarantees. As far as visible results go, there is no guarantee that thread A will see them in the order that thread B executes them. Brian even invoked special relativity to describe the disorienting effects of relative views of reality. You need synchronization to get inter-thread visibility guarantees.

The basic tools of thread synchronization are:

  • The synchronized keyword: an unlock happens-before every subsequent lock on the same monitor.
  • The volatile keyword: a write to a volatile variable happens-before subsequent reads of that variable.
  • Static initialization: done by the class loader, so the JVM guarantees thread safety

In addition to the above, the JMM offers a guarantee of initialization safety for immutable objects.

The Rules

Here are points that Brian emphasized:

  • If you read or write a field that is read/written by another thread, you must synchronize. This must be done by both the reading and writing threads, and on the same lock.
  • Don't try to reason about ordering in undersynchronized programs. 
  • Avoiding synchronization can cause subtle bugs that only blow up in production. Do it right first, then make it fast.

Case study: double-checked locking

One example of synchronization avoidance gone bad is the popular double-checked locking idiom for lazy initialization, which we now know is broken:

private Thing instance = null;
public Thing getInstance() {
if (instance == null) {
synchronized (this) {
if (instance == null) instance = new Thing();
}
}
return instance;
}


This idiom can result in a partially constructed Thing object, because it only worries about atomicity at the expense of visibility. There are ways to fix this, of course, such as using a volatile field or switching to using static initializers. But it's easy to get it wrong, so Brian questions why we would want to do something like this in the first place.

The main motivation was to avoid synchronization in the common case. While it used to be expensive in the past, uncontended synchronization is much cheaper now. There is still a lot of advice to avoid supposedly expensive Java operations out there, but the JVM has improved tremendously and a lot of old performance tips (like object pooling) just don't make sense anymore. Beware of reading years-old advice when you Google for Java tips. Remember Brian's advice above against premature optimization. That said, he also showed a couple of better alternatives for lazy initialization.

Some thoughts

This talk was a reminder to me that low-level multithreading is hard. It's hard enough that it took years to get the JMM right. It's hard enough that a university professor would say "don't do it". And if you faithfully follow Brian's rules and use synchronization primitives everywhere, you might find yourself vulnerable to thread deadlocks (hmmm ... why does JConsole have a deadlock detection function?).

The primary danger in multithreading is in shared, mutable state. Without shared mutable data, threads might as well be separate processes, and the danger evaporates. So while it's wonderful what JMM has done for cross-platform visibility guarantees, I think we would do ourselves a favor if we tried to minimize shared mutable data. There are often higher level alternatives. For example, Scala's Actor construct relies on passing immutable messages instead of sharing memory.

From http://chriswongdevblog.blogspot.com/

Memory architecture Memory model (programming) Java memory model Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Building Your Own Automatic Garbage Collector: A Guide for Developers
  • JVM Memory Architecture and GC Algorithm Basics
  • Java Object Size: Estimating, Measuring, and Verifying via Profiling
  • Java Thread Synchronization and Concurrency Part 1

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!