|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 3
Even if you were able to guarantee that writes to memory are completed in the desired order, that still might not be enough to render your programs correct with respect to the JMM. It is actually possible, under the current JMM, to force Java to update several variables to main memory in a specific order. You could do this:
class FullMemoryBarrierSingleton {
private static boolean initialized = false;
private static Resource resource = null;
private static Object lock = new Object();
public static Resource getResource() {
if (!initialized) {
synchronized (lock) {
if (!initialized && resource == null)
resource = new Resource();
}
synchronized (lock) {
initialized = true;
}
}
return resource;
}
}
The current JMM does not permit the JVM to merge two synchronized blocks. So another thread could not possibly see initialized set before resource and all its fields are fully initialized and written to main memory. So have we solved the DCL problem? FullMemoryBarrierSingleton appears to avoid synchronization on the most common code path, without any obvious memory model hazards. Unfortunately, this
is not exactly true.
Some processors exhibit cache coherency, which means that regardless of the contents of any given processor's cache, each processor sees the same values of memory. (Of course, depending on what is cached where, access to certain memory locations might be faster from some processors than from others.) While cache coherency certainly makes life easier for programmers, it substantially complicates the hardware and limits how many processors can connect to the same memory bus.
An alternative architectural approach to cache coherency is to allow each processor to update its own cache independently, but offer some sort of synchronization mechanism to make sure that updates by one processor become visible to other processors in a deterministic manner. That mechanism is generally the memory barrier, and many processors, such as Alpha, PowerPC, and Sparc offer explicit memory barrier instructions. Generally, there are two types of memory barrier instructions:
On architectures without cache coherency, two processors can potentially fetch a value from the same memory address and see different results. To avoid this problem, either the programmer or compiler must use memory barrier instructions.
The JMM was designed to support architectures both with and without cache coherency. The JMM requires that a thread perform
a read barrier after monitor entry and a write barrier before monitor exit. FullMemoryBarrierSingleton does indeed force the initializing thread to perform two write barriers so that resource and initialized are written to main memory in the proper order. So what could be wrong? The problem is that the other threads don't necessarily
perform a read barrier after determining that initialized is set, so they could possibly see stale values of resource or resource's fields.
To see how a thread could see stale values for resource, don't think in terms of objects and fields, but instead in terms of memory locations and their contents. Perhaps the memory
location corresponding to the field resource was already in the current processor's cache before another processor initialized resource. Since the current processor has not performed a read barrier, it would see that the address of resource was already in its cache and just use the cached value, which is now stale. The same could happen with any nonvolatile field
of resource. So by not synchronizing before acquiring the reference to resource, a thread might see a stale or garbage value for resource (or one of its fields).
Most Java applications are hosted on Intel or Sparc systems, which offer stronger memory models than required by the Java Memory Model. (Sparc processors actually offer multiple memory models with varying levels of cache coherency.) And many systems have only a single processor. It might be tempting to dismiss these concerns as being only of theoretical value and think, "That couldn't happen to us because we only use Solaris," or, "All our systems have single processors."
The danger behind dismissing these concerns is that these assumptions get buried in the code, where no one knows about them. Programs have a tendency to live much longer than expected. The Y2K phenomenon was dramatic evidence of that fact -- programmers made memory optimizations 20 or 30 years ago, fully convinced that future programmers would certainly replace the code by the year 2000. Even if you're sure that your program is only going to run on Linux/Intel in the foreseeable future, how do you know the same program won't get rehosted to another platform 10 years from now? Will anyone remember that you assumed otherwise when you wrote some unsynchronized cache class deeply buried inside your application?
DCL, and other techniques for avoiding synchronization, expose many of the complexities of the JMM. The issues surrounding synchronization are subtle and complicated -- so it is no surprise that many intelligent programmers have tried, but failed, to fix DCL.
The original goal of the Java Memory Model was to enable programmers to write concurrent programs in Java that would run efficiently on modern hardware, while still guaranteeing the Write Once, Run Anywhere behavior across a variety of computing architectures. Since there is now a JVM for nearly every conceivable processor, it is not unreasonable to expect that your code will eventually run on a different architecture than the one on which it was developed. So follow the rules now (synchronize!), and you can avoid a concurrency crisis in the future.
synchronized