[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
static
to makebed
variable andgetBed()
method.bed
variable 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
synchronized
to 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
cnt
is 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