[Design Pattern](EN) Singleton Pattern Concept and Example
Update(2020.07.18): Modify example and code.
Post about Singleton Pattern.
Environment and Prerequisite
- Java
What is Singleton Pattern?
Singleton Pattern
Singleton Pattern: Software design pattern that restricts the instantiation of a class to one single instance. It access to its instance by using like getInstance() method or function.- Image Source: https://en.wikipedia.org/wiki/Singleton_pattern
Implementation
Usual Case Implementation
- Let’s imagine we make bed.
- Consider bed should be only one instance, so then we can use singleton pattern like below.
Bed
public class Bed {
private static Bed bed = null;
private Bed() {
}
public static Bed getBed() {
if (bed == null) {
bed = new Bed();
}
return bed;
}
public void print(String str) {
System.out.println(str);
}
}
User
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void print() {
Bed.getBed().print(this.name + " " + "is in" + " " + Bed.getBed().toString() + " " + "bed");
}
}
Main
public class Main {
public static void main(String[] args) {
final int NUM_OF_USERS = 6;
User[] users = new User[NUM_OF_USERS];
for (int i = 0; i < NUM_OF_USERS; ++i) {
users[i] = new User(i + "-user");
users[i].print();
}
}
}
Result
0-user is in com.company.Bed@60e53b93 bed
1-user is in com.company.Bed@60e53b93 bed
2-user is in com.company.Bed@60e53b93 bed
3-user is in com.company.Bed@60e53b93 bed
4-user is in com.company.Bed@60e53b93 bed
5-user is in com.company.Bed@60e53b93 bed
- Above class makes instance if there is no instance. If instance exists, then return the instance.
- Use
staticto makebedvariable andgetBed()method.bedvariable andgetBed()method are belong to class so it can be used without instance. - By using like this, we can make only one instance.
- Result shows that there is only one bed instance.
Problem
- There can be a race condition if multi threads access to this instance simultaneously before instantiation.
- There can be multiple instances if threads access simultaneously before making instance.
- Below example uses
Thread.sleep(10)to make multiple instances.
Bed
public class Bed {
private static Bed bed = null;
private Bed() {
}
public static Bed getBed() {
if (bed == null) {
try {
Thread.sleep(10);
} catch (Exception e) {
System.out.println(e.toString());
}
bed = new Bed();
}
return bed;
}
public void print(String str) {
System.out.println(str);
}
}
User
public class User extends Thread {
private String name;
public User(String name) {
this.name = name;
}
public void run() {
Bed.getBed().print(this.name + " " + "is in" + " " + Bed.getBed().toString() + " " + "bed");
}
}
Main
public class Main {
public static void main(String[] args) {
final int NUM_OF_USERS = 6;
User[] users = new User[NUM_OF_USERS];
for (int i = 0; i < NUM_OF_USERS; ++i) {
users[i] = new User(i + "-user");
users[i].start();
}
}
}
Result
0-user is in com.company.Bed@4d0c434e bed
2-user is in com.company.Bed@4d0c434e bed
5-user is in com.company.Bed@135b88ad bed
1-user is in com.company.Bed@17733f02 bed
3-user is in com.company.Bed@4d0c434e bed
4-user is in com.company.Bed@2c79921e bed
- Like this, if threads access simultaneously then it fails to make one instance in singleton pattern.
Solution - Instantiate when initialize class
- Make instance immediately when make variable in class.
- Like
private static final Bed bed = new Bed();, make instance immediately.
Bed
public class Bed {
private static final Bed bed = new Bed();
private Bed() {
}
public static Bed getBed() {
if (bed == null) {
try {
Thread.sleep(10);
} catch (Exception e) {
System.out.println(e.toString());
}
}
return bed;
}
public void print(String str) {
System.out.println(str);
}
}
User
public class User extends Thread {
private String name;
public User(String name) {
this.name = name;
}
public void run() {
Bed.getBed().print(this.name + " " + "is in" + " " + Bed.getBed().toString() + " " + "bed");
}
}
Main
public class Main {
public static void main(String[] args) {
final int NUM_OF_USERS = 6;
User[] users = new User[NUM_OF_USERS];
for (int i = 0; i < NUM_OF_USERS; ++i) {
users[i] = new User(i + "-user");
users[i].start();
}
}
}
Result
1-user is in com.company.Bed@6fa76e7 bed
3-user is in com.company.Bed@6fa76e7 bed
2-user is in com.company.Bed@6fa76e7 bed
0-user is in com.company.Bed@6fa76e7 bed
4-user is in com.company.Bed@6fa76e7 bed
5-user is in com.company.Bed@6fa76e7 bed
- All threads access to only one instance.
Solution - Synchronize instance construction method
- Use
synchronizedto prevent from accessing multiple threads to method simultaneously.
Bed
public class Bed {
private static Bed bed = null;
private Bed() {
}
public synchronized static Bed getBed() {
if (bed == null) {
try {
Thread.sleep(10);
} catch (Exception e) {
System.out.println(e.toString());
}
bed = new Bed();
}
return bed;
}
public void print(String str) {
System.out.println(str);
}
}
User
public class User extends Thread {
private String name;
public User(String name) {
this.name = name;
}
public void run() {
Bed.getBed().print(this.name + " " + "is in" + " " + Bed.getBed().toString() + " " + "bed");
}
}
Main
public class Main {
public static void main(String[] args) {
final int NUM_OF_USERS = 6;
User[] users = new User[NUM_OF_USERS];
for (int i = 0; i < NUM_OF_USERS; ++i) {
users[i] = new User(i + "-user");
users[i].start();
}
}
}
Result
5-user is in com.company.Bed@6fa76e7 bed
2-user is in com.company.Bed@6fa76e7 bed
0-user is in com.company.Bed@6fa76e7 bed
4-user is in com.company.Bed@6fa76e7 bed
3-user is in com.company.Bed@6fa76e7 bed
1-user is in com.company.Bed@6fa76e7 bed
- All threads access to only one instance.
Problem
- Even though above multiple thread problems are solved, if there is variable in class like below then there comes another problem.
cnt’s value is not consistent.
Bed
public class Bed {
private static Bed bed = null;
private static int cnt = 0;
private Bed() {
}
public synchronized static Bed getBed() {
if (bed == null) {
bed = new Bed();
}
return bed;
}
public void print(String str) {
cnt++;
System.out.println(str + " " + "cnt: " + cnt);
}
}
User
public class User extends Thread {
private String name;
public User(String name) {
this.name = name;
}
public void run() {
Bed.getBed().print(this.name + " " + "is in" + " " + Bed.getBed().toString() + " " + "bed");
}
}
Main
public class Main {
public static void main(String[] args) {
final int NUM_OF_USERS = 6;
User[] users = new User[NUM_OF_USERS];
for (int i = 0; i < NUM_OF_USERS; ++i) {
users[i] = new User(i + "-user");
users[i].start();
}
}
}
Result
1-user is in com.company.Bed@1f904935 bed cnt: 2
4-user is in com.company.Bed@1f904935 bed cnt: 2
0-user is in com.company.Bed@1f904935 bed cnt: 2
3-user is in com.company.Bed@1f904935 bed cnt: 3
2-user is in com.company.Bed@1f904935 bed cnt: 3
5-user is in com.company.Bed@1f904935 bed cnt: 4
Solution - Add synchronized in method
- Cover method with
synchronized()to prevent from simultaneous threads access. synchronized(this)means sync to its instance.
Bed
public class Bed {
private static Bed bed = null;
private static int cnt = 0;
private Bed() {
}
public synchronized static Bed getBed() {
if (bed == null) {
bed = new Bed();
}
return bed;
}
public void print(String str) {
synchronized (this) {
cnt++;
System.out.println(str + " " + "cnt: " + cnt);
}
}
/*
public synchronized void print(String str) {
cnt++;
System.out.println(str + " " + "cnt: " + cnt);
}
*/
}
User
public class User extends Thread {
private String name;
public User(String name) {
this.name = name;
}
public void run() {
Bed.getBed().print(this.name + " " + "is in" + " " + Bed.getBed().toString() + " " + "bed");
}
}
Main
public class Main {
public static void main(String[] args) {
final int NUM_OF_USERS = 6;
User[] users = new User[NUM_OF_USERS];
for (int i = 0; i < NUM_OF_USERS; ++i) {
users[i] = new User(i + "-user");
users[i].start();
}
}
}
Result
0-user is in com.company.Bed@4f90c612 bed cnt: 1
3-user is in com.company.Bed@4f90c612 bed cnt: 2
2-user is in com.company.Bed@4f90c612 bed cnt: 3
1-user is in com.company.Bed@4f90c612 bed cnt: 4
4-user is in com.company.Bed@4f90c612 bed cnt: 5
5-user is in com.company.Bed@4f90c612 bed cnt: 6
- Now
cntis printed well and there is only one instance.
Comparison between singleton pattern and static class
- Static class can be also used without instantiation and used in global area. However static class cannot implement interface but singleton pattern can implement interface.
- Stack Overflow Link: https://stackoverflow.com/questions/519520/difference-between-static-class-and-singleton-pattern