Date: Tue, 19 Mar 2024 01:49:24 -0400 (EDT) Message-ID: <126827267.1.1710827364432@wilbert.sei.cmu.edu> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_0_1998105686.1710827364409" ------=_Part_0_1998105686.1710827364409 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
According to the Java Language Specification, =C2=A78.3.1.4, "volatile=
code> Fields"
[JLS 2013],
A field may be declared
volatile
, in whic= h case the Java Memory Model ensures that all threads see a consistent valu= e for the variable (=C2=A71= 7.4).
This safe publication guarantee applies only to primitive fields and obj= ect references. Programmers commonly use imprecise terminology and speak ab= out "member objects." For the purposes of this visibility guarantee, the ac= tual member is the object reference; the objects referred to (aka refer= ents) by volatile object references are beyond the scope of the safe p= ublication guarantee. Consequently, declaring an object reference to be vol= atile is insufficient to guarantee that changes to the members of the refer= ent are published to other threads. A thread may fail to observe a recent w= rite from another thread to a member field of such an object referent.
Furthermore, when the referent is mutable and lacks thread safety, other=
threads might see a partially constructed object or an object in a (tempor=
arily) inconsistent state [Goetz 2007]. However, when the refer=
ent is immutable, declaring the r=
eference volatile suffices to guarantee safe publication of the members of =
the referent. Programmers cannot use the volatile
keyword to g=
uarantee safe publication of mutable objects. Use of the volatile keyword can only guarantee safe publication of primitive fields, object=
references, or fields of immutable object referents.
Confusing a volatile object with the volatility of its member objects is= a similar error to the one described in OBJ50-J. Never confuse the immutability of a reference wi= th that of the referenced object.
This noncompliant code example declares a volatile reference to an array= object.
final c= lass Foo { private volatile int[] arr =3D new int[20]; public int getFirst() { return arr[0]; } public void setFirst(int n) { arr[0] =3D n; } // ... }
Values assigned to an array element by one thread=E2=80=94for example, b=
y calling setFirst()
=E2=80=94might not be visible to another t=
hread calling getFirst()
because the volatile
key=
word guarantees safe publication only for the array reference; it makes no =
guarantee regarding the actual data contained within the array.
This problem arises when the thread that calls setFirst()
a=
nd the thread that calls getFirst()
lack a happens-before relationship. A happens-before relationship exis=
ts between a thread that writes to a volatile variable and a threa=
d that subsequently reads it. However, setFirst()
and=
getFirst()
read only from a volatile variable=E2=80=94the vol=
atile reference to the array. Neither method writes to the volatile variabl=
e.
AtomicIntegerArray
)To ensure that the writes to array elements are atomic and that the resu=
lting values are visible to other threads, this compliant solution uses the=
AtomicIntegerArray
class defined in java.util.concurren=
t.atomic
.
final c= lass Foo { private final AtomicIntegerArray atomicArray =3D=20 new AtomicIntegerArray(20); public int getFirst() { return atomicArray.get(0); } public void setFirst(int n) { atomicArray.set(0, 10); } // ... }
To ensure visibility, accessor methods may synchronize access while perf=
orming operations on nonvolatile elements of an array, whether the array is=
referred to by a volatile or a nonvolatile reference. Note that the code i=
s thread-safe even though the array reference is not volatile. Synchronization establishes a happens-before relati=
onship between threads that synchronize on the same lock. In this case, the=
thread that calls This noncompliant code example declares the Interleaved calls to This noncompliant code example attempts to use the volatile-read, synchr=
onized-write technique described in Java Theory and Practice [Goetz 2007]. The The volatile-read, synchronized-write technique uses synchronization to =
preserve atomicity of compound operations, such as increment, and provides =
faster access times for atomic reads. However, it fails for mutable objects=
because the safe publication guarantee provided by This technique is also discussed in VNA=
02-J. Ensure that compound operations on shared variables are atomic.=
p>
This compliant solution uses method synchronization to guarantee visibil=
ity: It is unnecessary to declare the In this noncompliant code example, the volatile Because This compliant solution creates and returns a new This compliant solution makes This compliant solution uses a Incorrectly assuming that declaring a field volatile guarantees safe pub=
lication of a referenced object's members can cause threads to observe stal=
e or inconsistent values. Technically, strict immutability of the referent is a stronger condition=
than is fundamentally required for safe publication. When it can be determ=
ined that a referent is thread-safe by design, the field that holds its ref=
erence may be declared volatile. However, this approach to using AtomicIntegerArray
guarantees a happens-before relationship between a thread that calls atomicArray.set()=
code> and a thread that subsequently calls
atomicArray.get()
.<=
/p>
Compliant Solution (Synchronization)
final c=
lass Foo {
private int[] arr =3D new int[20];
public synchronized int getFirst() {
return arr[0];
}
public synchronized void setFirst(int n) {
arr[0] =3D n;
}
}
setFirst()
and the thread that subsequently=
calls getFirst()
on the same object instance both synchronize=
on that instance, so safe publication is guaranteed.Noncompliant Code Example (Mutable Object)
Map
instan=
ce field volatile. The instance of the Map
object is muta=
ble because of its put()
method.final c=
lass Foo {
private volatile Map<String, String> map;
=20
public Foo() {
map =3D new HashMap<String, String>();
// Load some useful values into map
}
=20
public String get(String s) {
return map.get(s);
}
=20
public void put(String key, String value) {
// Validate the values before inserting
if (!value.matches("[\\w]*")) {
throw new IllegalArgumentException();
}
map.put(key, value);
}
}
get()
and put()
may resul=
t in the retrieval of internally inconsistent values from the Map object because
put()
modifies its state. Declaring th=
e object reference volatile is insufficient to eliminate this data race.Noncompliant Code Example (Volatile-Read, Synchr=
onized-Write)
map field is declared volatile to synchronize its reads and writes. The=
put()
method is also synchronized to ensure that its statemen=
ts are executed atomically.final c=
lass Foo {
private volatile Map<String, String> map;
public Foo() {
map =3D new HashMap<String, String>();
// Load some useful values into map
}
public String get(String s) {
return map.get(s);
}
public synchronized void put(String key, String value) {
// Validate the values before inserting
if (!value.matches("[\\w]*")) {
throw new IllegalArgumentException();
}
map.put(key, value);
}
}
volatile
e=
xtends only to the field itself (the primitive value or object reference); =
the referent is excluded from the guarantee, as are the referent's members.=
In effect, the write and a subsequent read of the map
lack a&=
nbsp;happens-before relationship.Compliant Solution (Synchronized)
final c=
lass Foo {
private final Map<String, String> map;
public Foo() {
map =3D new HashMap<String, String>();
// Load some useful values into map
}
public synchronized String get(String s) {
return map.get(s);
}
public synchronized void put(String key, String value) {
// Validate the values before inserting
if (!value.matches("[\\w]*")) {
throw new IllegalArgumentException();
}
map.put(key, value);
}
}
map
field volatile be=
cause the accessor methods are synchronized. The field is declared fi=
nal
to prevent publication of its reference when the referent is in =
a partially initialized state (see TSM03-J. Do not publish p=
artially initialized objects for more information).Noncompliant Code Example (Mutable Subobject)
format
fiel=
d stores a reference to a mutable object, java.text.DateFormat
=
:final c=
lass DateHandler {
private static volatile DateFormat format =3D
DateFormat.getDateInstance(DateFormat.MEDIUM);
public static java.util.Date parse(String str)=20
throws ParseException {
return format.parse(str);
}
}
DateFormat
is not thread-safe [API 2013],=
the value for Date
returned by the parse()
metho=
d may not correspond to the str
argument:Compliant Solution (Instance per Call/Defensive Copyin=
g)
DateFormat
instance for each invocation of the parse()
method [A=
PI 2013]:final c=
lass DateHandler {
public static java.util.Date parse(String str)=20
throws ParseException {
return DateFormat.getDateInstance(
DateFormat.MEDIUM).parse(str);
}
}
Compliant Solution (Synchronization)
DateHandler
thread-safe by sy=
nchronizing statements within the parse()
method [API 20=
13]:final c=
lass DateHandler {
private static DateFormat format =3D
DateFormat.getDateInstance(DateFormat.MEDIUM);
public static java.util.Date parse(String str)=20
throws ParseException {
synchronized (format) {
return format.parse(str);
}
}
}
Compliant Solution (
ThreadLocal
Storage)ThreadLocal
object to create=
a separate DateFormat
instance per thread:final c=
lass DateHandler {
private static final ThreadLocal<DateFormat> format =3D=20
new ThreadLocal<DateFormat>() {
@Override protected DateFormat initialValue() {
return DateFormat.getDateInstance(DateFormat.MEDIUM);
}
};
// ...
}
Applicability
vola=
tile
decreases maintainability and should be avoided.Bibliography