[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 make bed variable and getBed() method. bed variable and getBed() 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


Reference