Ludzie pragną czasami się rozstawać, żeby móc tęsknić, czekać i cieszyć się z powrotem.
Feedback
The atomic operations commonly mentioned in this lore include simple
assignment and returning a value when the variable in question is a
primitive type that is not a long or a double. The latter types are excluded because they are larger than the rest of the types, and the JVM is
thus not required to perform reads and assignments as single atomic
operations (a JVM may choose to do so, anyway, but there’s no
guarantee). However, you do get atomicity if you use the volatile
keyword with long or double. Feedback
If you were to blindly apply the idea of atomicity to
SynchronizedEvenGenerator.java, you would notice that
public synchronized int getValue() { return i; }
fits the description. But try removing synchronized and the test will fail,
because even though return i is indeed an atomic operation, removing
synchronized allows the value to be read while the object is in an
unstable intermediate state. You must genuinely understand what you’re
doing before you try to apply optimizations like this. There are no easily-
applicable rules that work. Feedback
As a second example, consider something even simpler: a class that
produces serial numbers3. Each time nextSerialNumber( ) is called, it
must return a unique value to the caller:
3 Inspired by Joshua Bloch’s Effective Java, Addison-Wesley 2001, page 190.
Chapter 13: Concurrency
745
//: c13:SerialNumberGenerator.java
public class SerialNumberGenerator {
private static volatile int serialNumber = 0;
public static int nextSerialNumber() {
return serialNumber++;
}
} ///:~
SerialNumberGenerator is about as simple a class as you can imagine,
and if you’re coming from C++ or some other low-level background, you
would expect the increment to be an atomic operation, because increment
is usually implemented as a microprocessor instruction. However, in the
JVM an increment is not atomic and involves both a read and a write, so
there’s room for threading problems even in such a simple operation.
Feedback
The serialNumber field is volatile because it is possible for each thread
to have a local stack and maintain copies of some variables there. If you
define a variable as volatile, it tells the compiler not to do any
optimizations that would remove reads and writes that keep the field in
exact synchronization with the local data in the threads. Feedback
To test this, we need a set which doesn’t run out of memory, in case it
takes a long time to detect a problem. The CircularSet shown here
reuses the memory used to store ints, with the assumption that by the
time you wrap around, the possibility of a collision with the overwritten
values is minimal. The add( ) and contains( ) methods are
synchronized to prevent thread collisions: Feedback
//: c13:SerialNumberChecker.java
// Operations that may seem safe are not,
// when threads are present.
// Reuses storage so we don't run out of memory:
class CircularSet {
private int[] array;
private int len;
private int index = 0;
public CircularSet(int size) {
array = new int[size];
len = size;
// Initialize to a value not produced
746
Thinking in Java
www.BruceEckel.com
// by the SerialNumberGenerator:
for(int i = 0; i < size; i++)
array[i] = -1;
}
public synchronized void add(int i) {
array[index] = i;
// Wrap index and write over old elements:
index = ++index % len;
}
public synchronized boolean contains(int val) {
for(int i = 0; i < len; i++)
if(array[i] == val) return true;
return false;
}
}
public class SerialNumberChecker {
private static CircularSet serials =
new CircularSet(1000);
static class SerialChecker extends Thread {
SerialChecker() { start(); }
public void run() {
while(true) {
int serial =
SerialNumberGenerator.nextSerialNumber();
if(serials.contains(serial)) {
System.out.println("Duplicate: " + serial);
System.exit(0);
}
serials.add(serial);
}
}
}
public static void main(String args[]) {
for(int i = 0; i < 10; i++)
new SerialChecker();
// Stop after 4 seconds:
new Timeout(4000, "No duplicates detected");
}
} ///:~
SerialNumberChecker contains a static CircularSet which contains
all the serial numbers that have been extracted, and a nested Thread that
gets serial numbers and ensures that they are unique. By creating multiple
Chapter 13: Concurrency
747
threads to contend over serial numbers, you’ll discover that the threads
get a duplicate serial number reasonably soon (note that this program
may not indicate a collision on your machine, but it has successfully
detected collisions on a multiprocessor machine). To solve the problem,
add the synchronized keyword to nextSerialNumber( ). Feedback
The atomic operations that are supposed to be safe are reading and
assignment of primitives. However, as seen in EvenGenerator.java, it’s
still easily possible to use an atomic operation that accesses your object
while it’s in an unstable intermediate state, and so you cannot make any
assumptions. On top of this, the atomic operations are not guaranteed to