Author |
Topic: Singleton Pattern |
|
Alex_Raj member offline |
|
posts: |
99 |
joined: |
05/16/2006 |
from: |
San Jose, CA |
|
|
|
|
|
Singleton Pattern |
As its name suggests, singleton is a class that is instantiated only once across your application environment. This is typically accomplished by creating a static field in the class representing the class.
Two key points of a single design pattern: only one instance allowed for a class global point of access to that single instance
Here is an example (not right though):
public class Singleton
{
/* static field to ensure only one copy */
private static Singleton instance;
/* private constructor to prevent it from being instantiated from outside */
private Singleton() {
}
/* public static method to ensure global point of access */
public static Singleton getInstance()
{
if(instance==null){
instance = new Singleton();
}
return instance;
}
public void sayHello() {
System.out.println("Hello");
}
}
|
|
|
|
|
|
|
Alex_Raj member offline |
|
posts: |
99 |
joined: |
05/16/2006 |
from: |
San Jose, CA |
|
|
|
|
|
What we can learn from Singleton Pattern -- Concurrent Access Issue |
What's wrong with the example shown above? Yes, it's not thread-safe.
The singleton pattern must be carefully constructed in multi-threaded applications. If two threads are to execute the creation method at the same time when a singleton does not yet exist, they both end up with creating their own instances -- which contradicts the definition or purpose of singleton at the first place. Moreover, if the creation of singleton involves grabbing a mutual exclusive resource (e.g. disk file holder or database connection), the second instance of the singleton may either gets blocked or holds a null reference to the underlying resource.
There is the thread-safe version:
/**
* Synchronized on method getInstance()
*/
public class Singleton
{
/* static field to ensure only one copy */
private static Singleton instance;
/* private constructor to prevent it from being instantiated from outside */
private Singleton() {
}
/* public static method to ensure global point of access */
public static synchronized Singleton getInstance()
{
if(instance==null){
instance = new Singleton();
}
return instance;
}
public void sayHello() {
System.out.println("Hello");
}
}
OR
/**
* Synchronized on block
*/
public class Singleton
{
/* static field to ensure only one copy */
private static Singleton instance;
/* private constructor to prevent it from being instantiated from outside */
private Singleton() {
}
/* public static method to ensure global point of access */
public static Singleton getInstance()
{
synchronized(Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
return instance;
}
public void sayHello() {
System.out.println("Hello");
}
}
|
|
|
|
|
|
|
Alex_Raj member offline |
|
posts: |
99 |
joined: |
05/16/2006 |
from: |
San Jose, CA |
|
|
|
|
|
What we can learn from Singleton Pattern -- Code Efficiency |
What's wrong with the example shown above this time? Nothing is wrong here, but it's just not efficient.
You might have noticed the cost of synchronization -- there will be an extra cycle to grab and release the sync lock each time to get the instance. Only the first time is really needed and the rest are just pure waste of time.
Here comes the variation to enter the sync block only once:
/**
* Synchronized on block - double-checked locking
*/
public class Singleton
{
/* static field to ensure only one copy */
private static Singleton instance;
/* private constructor to prevent it from being instantiated from outside */
private Singleton() {
}
/* public static method to ensure global point of access */
public static Singleton getInstance()
{
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
public void sayHello() {
System.out.println("Hello");
}
}
|
|
|
|
|
|
|
Alex_Raj member offline |
|
posts: |
99 |
joined: |
05/16/2006 |
from: |
San Jose, CA |
|
|
|
|
|
What we can learn from Singleton Pattern -- Double-checked Locking Issue |
What's wrong with the example shown above then? Let's look at this piece:
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
It's well known "double-checked locking" trick. But is doesn't work for JDK prior to 1.5.0. The compiler optimization could mess it up by making the assignment of the new Singleton object before all its fields are initialized. This is no longer an issue after JDK 1.5.0.
|
|
|
|
|
|
|
Alex_Raj member offline |
|
posts: |
99 |
joined: |
05/16/2006 |
from: |
San Jose, CA |
|
|
|
|
|
What we can learn from Singleton Pattern -- There is always an alternative way. |
Early Instantiation Version:
If the program will always need an instance, or if the cost of creating the instance is not too large in terms of time/resources, the programmer can switch to eager initialization, which always creates an instance:
/**
* Early instantiation version - no synchronization is needed
*/
public class Singleton
{
/* static field to ensure only one copy */
private static final Singleton INSTANCE = new Singleton();
/* private constructor to prevent it from being instantiated from outside */
private Singleton() {
}
/* public static method to ensure global point of access */
public static Singleton getInstance() {
return INSTANCE;
}
public void sayHello() {
System.out.println("Hello");
}
}
This version has a number of advantages: The instance is not constructed until the class is used. There is no need to synchronize the getInstance() method, meaning all threads will see the same instance and no (expensive) locking is required. The final keyword means that the instance cannot be redefined, ensuring that one (and only one) instance ever exists.
Lazy Inner Static Class Version:
/**
* Lazy inner static class version - no synchronization is needed
*/
public class Singleton
{
/* static class to ensure only one copy & lazy load as is needed*/
private static class SingletonHolder {
public static final Singleton INSTANCE = new Singleton();
}
/* private constructor to prevent it from being instantiated from outside */
private Singleton() {
}
/* public static method to ensure global point of access */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
public void sayHello() {
System.out.println("Hello");
}
}
This version has a number of advantages: The instance is not constructed until getInstance() method is used. There is no need to synchronize the getInstance() method, meaning all threads will see the same instance and no (expensive) locking is required. The final keyword means that the instance cannot be redefined, ensuring that one (and only one) instance ever exists.
|
|
|
|
|
|
|
Alex_Raj member offline |
|
posts: |
99 |
joined: |
05/16/2006 |
from: |
San Jose, CA |
|
|
|
|
|
What we can learn from Singleton Pattern -- Potential Concurrent Access Issues. |
Let's consider a real scenario: a sequence number or auto increment number is globally needed for all records or change events pushing into database. The number must be unique across all application environment. For this case, a sequence generator in singleton pattern is a perfect solution.
/**
* A Real Example: Sequence Generator -- (not right)
*/
public class SequenceGenerator
{
/* static field to ensure only one copy */
private static final SequenceGenerator INSTANCE = new SequenceGenerator();
private long sequenceNumber;
/* private constructor to prevent it from being instantiated from outside */
private SequenceGenerator() {
sequenceNumber = 0L; // or loading from persistence
}
/* public static method to ensure global point of access */
public static SequenceGenerator getInstance() {
return INSTANCE;
}
public long nextNumber() {
sequenceNumber++;
return sequenceNumber;
}
}
As singleton suggests only one instance and global accessibility, there is a very high chance that the instance methods are accessed concurrently due to the shared nature of the instance. Here in this example, the operation sequenceNumber++; is not atomic and hence the method nextNumber() must be mutual exclusively blocked.
/**
* A Real Example: Sequence Generator -- synchronized version
*/
public class SequenceGenerator
{
/* static field to ensure only one copy */
private static final SequenceGenerator INSTANCE = new SequenceGenerator();
private long sequenceNumber;
/* private constructor to prevent it from being instantiated from outside */
private SequenceGenerator() {
sequenceNumber = 0L; // or loading from persistence
}
/* public static method to ensure global point of access */
public static SequenceGenerator getInstance() {
return INSTANCE;
}
public synchronized long nextNumber() {
sequenceNumber++;
return sequenceNumber;
}
}
OR:
/**
* A Real Example: Sequence Generator -- atomic version
*/
import java.util.concurrent.atomic.AtomicLong;
public class SequenceGenerator
{
/* static field to ensure only one copy */
private static final SequenceGenerator INSTANCE = new SequenceGenerator();
private AtomicLong atomicSequenceNumber;
/* private constructor to prevent it from being instantiated from outside */
private SequenceGenerator() {
atomicSequenceNumber = new AtomicLong(0L); // or loading from persistence
}
/* public static method to ensure global point of access */
public static SequenceGenerator getInstance() {
return INSTANCE;
}
public long nextNumber() {
return atomicSequenceNumber.incrementAndGet();
}
}
|
|
|
|
|
|
|