Saturday, October 03, 2009

Ways by which singleton can be created

Singleton classes can be created in 3 ways

Case 1:

package org.vijayan.sample;

public class Singleton {

/**
* Advanced Initialization
*/
public static final Singleton SINGLETON=new Singleton();
private Singleton(){
}

public static void main(String[] args) {
for(int i=0;i<4;i++){
System.out.println(Singleton.SINGLETON);
}
}
}


In this case the statically created object is made final and publicly exposed to everyone.

Case 2:


package org.vijayan.sample;

public class Singleton {

/**
* Advanced Initialization
*/
private static Singleton singleton=new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return singleton;
}
public static void main(String[] args) {
for(int i=0;i<4;i++){
System.out.println(Singleton.getInstance());
}
}
}


Case 3:


package org.vijayan.sample;

public class Singleton {

/**
* Lazy Initialization
*/
private static Singleton singleton=null;
private Singleton(){
}

public static Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}

public static void main(String[] args) {
for(int i=0;i<4;i++){
System.out.println(Singleton.getInstance());
}
}
}


In this third case is preferable because it creates the object on-demand. But it can create issue when it is used in multi threaded environment.

So the code can be changed to case 4

Case 4:


package org.vijayan.sample;

public class Singleton {

/**
* Lazy Initialization
*/
private static Singleton singleton=null;
private Singleton(){
}
/**
*
* Will take care multi threaded environment but it expensive
*/
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
public static void main(String[] args) {
for(int i=0;i<4;i++){
System.out.println(Singleton.getInstance());
}
}
}


Though it takes care multi threaded this will become a costly method. to make it a light-weight we can change the code to case 5

Case 5:


package org.vijayan.sample;

public class Singleton {

/**
* Lazy Initialization
*/
private static Singleton singleton=null;
private Singleton(){
}
/**
*
* Will take care multithreaded environment also less expensive
*/
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class) {
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}

public static void main(String[] args) {
for(int i=0;i<4;i++){
System.out.println(Singleton.getInstance());
}
}

}


This method is an efficient but complex one, this is also called as double-null checking method. more info on this can be found in http://en.wikipedia.org/wiki/Double-checked_locking

Output:

org.vijayan.sample.Singleton@10b62c9
org.vijayan.sample.Singleton@10b62c9
org.vijayan.sample.Singleton@10b62c9
org.vijayan.sample.Singleton@10b62c9

Please note all 4 times it returns the same object reference.

Synchronization usecases

Consider a class called A, it has m1() and m2() methods, 2 instance of this A class is created a1 and a2 and there are 2 threads t1, t2

Case 1: m1() and m2() are not synchronized

t1 uses a1 and calls m1()

t2 uses a2 and calls m2()

both thread t1 and t2 can run at the same time because both threads are using different objects.

Case 2: m1() and m2() are synchronized

t1 uses a1 and calls m1()

t2 uses a2 and calls m2()

both thread t1 and t2 can run at the same time because both threads are using different objects.
by default synchronization will acquire lock on the object, since here both are using different object locking will not happen.

Case 3: m1() and m2() are synchronized

t1 uses a1 and calls m1()

t2 uses a1 and calls m2()

both thread t1 and t2 cannot run at the same time because both threads are using same object.
if t1 first access m1() then t2 will have to wait for t1 to complete running m1()

Case 4: m1() is static and m2() is non static and both are synchronized

t1 uses a1 and calls m1()

t2 uses a1 and calls m2()

both thread t1 and t2 can run at the same time because though both threads are using same object when t1 first access m1() it is not accessing via a1 object rather it will use A class for accessing the static method that is A.m1() is what called. For doing the same A.Class object will be created and it will be synchronized using A.Class object and t2 will be using a1 object for locking since both objects are different they can work parallel.

Case 5: m1() is static and m2() is static and both are synchronized

t1 uses a1 and calls m1()

t2 uses a1 and calls m2()

both thread t1 and t2 cannot run at the same time because though both threads are using same object (A.Class object)

Why synchronization in java?

Consider the following programs

package org.vijayan.sample;

public class Main {

public static void main(String[] args) {
SampleFile file=new SampleFile("test.txt");
FileWriterThread mt1=new FileWriterThread(10000,"MT1", file);
FileWriterThread mt2=new FileWriterThread(20,"MT2", file);
mt1.start();
mt2.start();
}

}

package org.vijayan.sample;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;


public class SampleFile {

private File file;
public SampleFile(String fileName){
file=new File(fileName);
file.delete();
}
public void writeMessage(int count, String message){
try {
FileWriter writer=new FileWriter(file,true);
for(int i=0;i
writer.write(message+"-"+i);
writer.write("\n");
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

package org.vijayan.sample;

public class FileWriterThread extends Thread {

private SampleFile file;
private int count;
public FileWriterThread(int count, String name, SampleFile math){
super(name);
this.file=math;
this.count=count;
}
@Override
public void run() {
file.writeMessage(count, getName());
}
}

Output of the above program can result

MT1-1026
MT1-1027
MT1-1028
MT1-1029
MT1-1030
MT1-1031
MT1-1032
MT1-1MT2-0
MT2-1
MT2-2
MT2-3
MT2-4
MT2-5
MT2-6
MT2-7
MT2-8
MT2-9
MT2-10
MT2-11
MT2-12
MT2-13
MT2-14
MT2-15
MT2-16
MT2-17
MT2-18
MT2-19
033
MT1-1034
MT1-1035
MT1-1036
MT1-1037

Please note the bold lines carefully. In this case both thread is using same SampleFile Object and both thread is writing to a same file concurrently. Since there is no synchronized keyword for writeMessae() it is possible. if we make this method synchronized like below

public synchronized void writeMessage(int count, String message){
try {
FileWriter writer=new FileWriter(file,true);
for(int i=0;i
writer.write(message+"-"+i);
writer.write("\n");
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}

then the output will change to like the following

MT1-9994
MT1-9995
MT1-9996
MT1-9997
MT1-9998
MT1-9999
MT2-0
MT2-1
MT2-2
MT2-3
MT2-4
MT2-5
MT2-6
MT2-7
MT2-8
MT2-9
MT2-10
MT2-11
MT2-12
MT2-13

Output 2 indicates that though both thread is started running the second thread is waiting until first thread completes its operation. though it is providing desired output we may not want to make the second thread wait until full completion of thread 1. This can be achieved by changing the program to following ways.


package org.vijayan.sample;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;


public class SampleFile {

private FileWriter writer;
public SampleFile(String fileName){
File file=new File(fileName);
file.delete();
try {
writer=new FileWriter(file,true);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
writer.close();
}
public void writeMessage(int count, String message){
try {
for(int i=0;i
synchronized (writer) {
writer.write(message+"-"+i);
writer.write("\n");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

Output:

MT1-1218
MT1-1219
MT2-0
MT2-1
MT2-2
MT2-3
MT2-4
MT2-5
MT2-6
MT2-7
MT2-8

Please note adding a synchronized block requires some extra code and it always needs to be done carefully.