|
|
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 4 of 5
This strategy of using the Class object's monitor as the critical-section lock doesn't always work out because you lock all the static methods of the class, not just the singleton-creation method. You can do the same thing with an explicitly declared singleton
lock as follows:
private static Object lock = new Object(); public static Singleton get_instance() // not synchronized { synchronized( lock ) { if( instance == null ) instance = new Singleton(); return instance; } }
This version still assures that only one instance of the singleton will be created, but it won't interfere with the execution of other static methods.
The main problem with this naive approach is efficiency. We acquire the lock every time we call get_instance(), even though the code only needs to be locked the first time the method is called. The solution to this problem is Doug Schmidt's
"double-checked locking" strategy. Here's the general pattern:
class Singleton { private Singleton instance; //... public static Singleton get_instance() // not synchronized { if( instance == null ) { synchronized( Std.class ) { if( instance == null ) instance = new Singleton(); } } return instance; } }
Most of the time, the object will exist when get_instance() is called, so we won't do any synchronization at all. On the first call, however, instance is null, so we enter the if statement and synchronize explicitly on the Class object to enter a critical section. Now we have to test for instance==null again, because we might have been preempted just after the first if was processed but before the synchronized statement was executed. If instance is still null, then no other thread will be creating the singleton, and we can create the object safely.
Listing 1 shows you a real-world application of a singleton that compensates for a problem in the design of the System class. A proper OO design never uses public fields except for symbolic constants, and I really mean "constant" here: The exposed field must be immutable,
not just final. (An object accessed via a final reference can be modified; an "immutable" object (like a String) can't be modified at all.) This rule applies to both "class" and "instance" variables, and there are no exceptions to this
rule. Ever. Period. Strong encapsulation of an object's implementation is so central to what "object orientation" means, that
this point is simply not negotiable. If you use public fields, your program just isn't object oriented -- it's some sort of
part-OO/part-procedural polyglot, and you will reap virtually none of the real benefits of OO such as improved maintenance.
The only legitimate public members of a class are those methods that handle messages defined in your design's dynamic-model.
The foregoing notwithstanding, there is one place in the Java packages where instance variables are exposed: System.in, System.out, and System.err. To my mind, this exposure is a serious design flaw: These fields are not Reader or Writer derivatives, so they are not internationalizable. Consequently, you can't use these variables without wrapping them in a
Reader or Writer. If System.in, System.out, and System.err had been accessed through "accessor" methods rather than directly, this wrapping could have been done transparently by the
(missing) method that returned the I/O stream. This method could have easily been modified to return a PrintWriter rather than a PrintStream without impacting much of the code that used it. As it is, there's a lot of incorrect code out there that uses the three
streams directly.