![](/style/images/good.png)
![](/style/images/bad.png)
On atomicity and tearing
source link: https://mail.openjdk.org/pipermail/valhalla-spec-experts/2023-April/002262.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
On atomicity and tearing
On atomicity and tearing
Brian Goetz
brian.goetz at oracle.com
Wed Apr 26 18:13:45 UTC 2023
There has been, not surprisingly, a lot of misunderstanding about atomicity, non-atomicity, and tearing. In particular, various syntactic expressions of non-atomicity (e.g., a `non-atomic` class keyword) tend to confuse users into thinking that non-atomic access is somehow a *feature*, rather than providing more precise control over the breakage modes of already-broken programs (to steer optimizations for non-broken programs.) I've written the following as an attempt to help people understand the role of atomicity and tearing in the model; comments are welcome (though let's steer clear of trying to paint the bikeshed in this thread.) # Understanding non-atomicity and tearing Almost since the beginning of Project Valhalla, the design has included some form of "non-atomicity" or "tearability". Addressing this in the programming model is necessary if we are to achieve the heap flattening that Valhalla wants to deliver, but unfortunately this aspect of the feature set is frequently misunderstood. Whether non-atomicity is expressed syntactically as a class modifier, constructor modifier, supertype, or some other means, the concept is the same: a class indicates its willingness to give up certain guarantees in order to gain additional heap flattening. Unlike most language features, which express either the presence or absence of things that are at some level "normal" (e.g., the presence or absence of `final` means a class either can be assigned to, or cannot), non-atomicity is different; it is about what the possible observable effects are when an instance of this class is accessed with a data race. Programs with data races are _already broken_, so rather than opting into or out of a feature, non-atomicity is expressing a choice between "breakage mode A" and "breakage mode B". > Non-atomicity is best thought of not as a _feature_ or the absence thereof, > but an alternate choice about the runtime-visible behavior of _already-broken > programs_. ## Background: flattening and tearing in built-in primitives Flattening and non-atomicity have been with us since Java 1.0. The eight built-in primitive types are routinely flattened into object layouts and arrays. This "direct" storage results from several design choices made about primitives: primitive types are non-nullable, and their zero values represent explicitly "good" default values and therefore even "uninitialized" primitives have useful initial values. Further, the two 64-bit primitive types (`long` and `double`) are explicitly permitted to _tear_ when accessed via a data race, as if they are read and written using two 32-bit loads and stores. When a mutable `long` or `double` is read with a data race, it may be seen to have the high-order 32 bits of one previous write and the low-order 32 bits of another. This is because at the time, atomic 64-bit loads and stores were prohibitively expensive on most processors, so we faced a tradeoff: punish well-behaved programs with poorly-performing numerics, or allow already-broken programs (concurrent programs with insufficient synchronization) to be seen to produce broken numeric results. In most similar situations, Java would have come down on the side of predictability and correctness. However, numeric performance was important enough, and data races enough of an "all bets are off" sort of thing, that this set of decisions was a pragmatic compromise. While tearing sounds scary, it is important to reiterate that tearing only happens when programs _are already broken_, and that even if we outlawed tearing, _something else bad_ would still happen. Valhalla takes these implicit characteristics of primitives and formalizes them to explicit characteristics of value classes in the programming model, enabling user-defined classes to gain the runtime characteristics of primitives. ## Data races and consistency A _data race_ is when a nonfinal heap variable (array element or nonfinal field) is accessed by multiple threads, at least once access is a write, and the reads and writes of that variable are not ordered by _happens-before_ (see JLS Ch17 or _Java Concurrency in Practice_ Ch16.) In the presence of a data race, the reading thread may see a stale (out of date) value for that variable. "Stale" doesn't sound so bad, but in a program with multiple variables, the error states can multiply with the number and configuration of mutable variables. Suppose we have two `Range` classes: ``` class MutableRange { int low, high; // obvious constructor, accessor, and updater methods // constructor and updater methods validate invariant low <= high } class ImmutableRange { final int low, high; // obvious constructor and accessors, constructor validates invariant } final static MutableRange mr = new MutableRange(0, 10); static ImmutableRange ir = new ImmutableRange(0, 10); ``` For `mr`, we have a final reference to a mutable point, so there are two mutable variables here (`mr.low` and `mr.high`.) We update our range value through a method that mutates `low` and/or `high`. By contrast, `ir` is a mutable reference to an immutable object, with one mutable variable (`ir`), and we update our range value by creating a new `ImmutableRange` and mutating the reference `ir` to refer to it. More things can go wrong when we racily access the mutable range, because there are more mutable variables. If Thread A writes `low` and then writes `high`, and Thread B reads `low` and `high`; under racy access B could see stale or up-to-date values for either field, and even if it sees an up-to-date value for `high` (the one written later), that still doesn't mean it would see an up-to-date value for `low`. This means that in addition to seeing out-of-date values for either or both, we could observe an instance of `MutableRange` to not obey the invariant that is checked by constructors and setters. Suppose instead we racily access the immutable range. At least there are fewer possible error states; a reader might see a stale _reference_ to the immutable object. Access to `low` and `high` through that stale reference would see out-of-date values, but those out-of-date values would at least be consistent with each other (because of the initialization safety guarantees of final fields.) When primitives other than `long` or `double` are accessed with a data race, the failure modes are like that of `ImmutableRange`; when we accept that `long` or `double` could tear under race, we are additionally accepting the failure modes of `MutableRange` under race for those types as well, as if the high- and low-order 32-bit quantities were separate fields (in exchange for better performance). Accepting non-atomicity of large primitives merely _increases_ the number of observable failure modes for broken programs; even with atomic access, such programs are still broken and can produce observably incorrect results. Note that a `long` or `double` will never tear if it is `final`, `volatile`, only accessed from a single thread, or accessed concurrently with appropriate sychronization. Tearing only happens in the presence of concurrent access to mutable variables with insufficient synchronization. ## Non-atomicity and value types Hardware has improved significantly since Java 1.0, so the specific tradeoff faced by the Java designers regarding `long` and `double` is no longer an issue, as most processors have fast atomic 64-bit load and store operations today. However, Valhalla will still face the same problem, as value types can easily exceed 64 bits in size, and whatever the limit on efficient atomic loads and stores is, we can easily write value types that will exceed that size. This leaves us with three choices: - Never allow tearing of values, as with `int`; - Always allow tearing of values under race, as with `long`; - Allow tearing of values under race based on some sort of opt-in or opt-out. Note that tearing is not anything anyone ever _wants_, but it is sometimes an acceptable tradeoff to get more flattening. It was a sensible tradeoff for `long` and `double` in 1995, and will continue to be a sensible tradeoff for at least some value types going forward. The first choice -- values are always atomic -- offers the most safety, but means we must forgo one of the primary goals of Valhalla for all but the smallest value types. This leaves us with "values are always like `long`", or "values can opt into / out of being like `long`." Types like `long` have the interesting property that all bit patterns correspond to valid values; there are no representational invariants for `long`. On the other hand, values are classes, and can have representation invariants that are enforced by the constructor. Having representational invariants for immutable classes be seen to not hold would be a significant and novel new failure mode, and so we took the safe route, requiring class authors to make the tradeoff between flattening and failure modes under race. Just as with `long` and `double`, a value will never tear if the variable that holds the value is `final`, `volatile`, only accessed from a single thread, or accessed concurrently with appropriate sychronization. Tearing only happens in the presence of concurrent access to mutable variables with insufficient synchronization. Further, tearing under race will only happen for non-nullable variables of value types that support default instances. What remains is to offer sensible advice to authors of value classes as to when to opt into non-atomicity. If a class has any cross-field invariants (such as `ImmutableRange`), atomicity should definitely be retained. In the remaining cases, class authors (like the creators of `long` or `double`) must make a tradeoff about the perceived value of atomicity vs flattening for the expected range of users of the class. -------------- next part -------------- An HTML attachment was scrubbed... URL: <https://mail.openjdk.org/pipermail/valhalla-spec-experts/attachments/20230426/0d5e6072/attachment-0001.htm>
More information about the valhalla-spec-experts
mailing list
Recommend
-
6
pmem.ioPersistent Memory Programming Using the Block Translation Table for sector atomicity Pe...
-
5
Jan 06 2022
-
2
Tearing Up the Carpet — Hi, I’m Sam Tearing Up the Carpet — Hi, I’m Sam
-
10
Arpit BhayaniIn this short essay, we dive deep and understand the "A" of ACID - Atomicity. In this quick read, we will take a detailed look into Atomicity, u...
-
1
Tearing out the Emacs windows manager 03 Oct 2013 Edit: I’ve created a small emacs package based on this post. There’s an old joke: “Emacs...
-
5
There’s a New Media Mogul Tearing Up Hollywood: ‘Zas Is Not Particularly Patient’CEO David Zaslav has quickly put his stamp on Warner Bros. Discovery, weighing in on content, forcing out executives and cutting costly proje...
-
4
Tearing down the Canon EOS R7 with Kolari Vision October 25, 2022 ...
-
4
After 4,368 Hours, ‘Mario Maker’ Player Finally Beats His Impossible LevelBraden "ChainChompBraden" Moor has spent six years chasing a nightmare, and it's finally come true. October 6, 2022, 1:...
-
2
Comment We are tearing up creative rights to feed a flawed Whitehall obsession with AI The Government's proposed overhaul of intellectual property laws is a mistake that will fail to deliver and risk...
-
7
Ripping and Tearing: 3 Decades of Doom By Rob Thubron December 11, 2023
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK