This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

The singleton pattern

define

  • This refers to ensuring that a class has only one instance and only one global access point in any case
  • It belongs to the creation pattern
  • Hide all of its constructors; Because the singleton pattern has only one instance globally, the user cannot create objects through constructors, only through my open interface

Common writing of singleton pattern

Henchman singleton: Creates instances when the singleton class is first loaded

The code shown

The first way to write it

public class SingletonPatternOne {

    private static final SingletonPatternOne instance = new SingletonPatternOne();

    private SingletonPatternOne(a) {}

    public SingletonPatternOne getInstance(a){
        returninstance; }}Copy the code

We need to privatize the constructor, because in a singleton there can only be one instance globally, so we want to privatize the constructor to prevent the user from creating the object through the new method

There is another way to write this, which is to put the code that creates the object into a static code block

public class SingletonPatternOne {

    private static final SingletonPatternOne instance;

    static {
        instance = new SingletonPatternOne();
    }

    private SingletonPatternOne(a) {}

    public SingletonPatternOne getInstance(a){
        returninstance; }}Copy the code

Note: The effect is the same

Q: why is it called hungry?

A: Because this method of creating objects is like a hungry man, who sees food and eats it. It is the same method of creating objects when this class is loaded.

The advantages and disadvantages

Advantages:

  • High execution efficiency, high performance, without any locks

Disadvantages:

  • Under certain conditions, memory may be wasted (for example, because the variable is static, it will be created when the class is loaded without using it), and it is not appropriate to use hunchman singletons if the project has a large number of singletons

Lazy singleton: created only when it is used by other classes

The lazy way is to name a variable first in the class and only create it when it is used. Create it the first time you use it and return the variable later

public class LazySingletonPattern {

    private static LazySingletonPattern instance;

    private LazySingletonPattern(a){}

    public static LazySingletonPattern getInstance(a){
        if(instance == null){
            instance = new LazySingletonPattern();
        }
        returninstance; }}Copy the code

Similarly to hanky-hank writing, constructors need to be privatized because users cannot create objects through constructors themselves

When you get an instance object, check if it was created and return the result if it was created, or create an object if it wasn’t

However: this approach can cause thread insecurity in multithreaded mode

The advantages and disadvantages

Advantages:

  • It saves memory and saves unnecessary memory consumption

Disadvantages:

  • There will be thread insecurity issues

What about this lazy thread insecurity?

Let’s start with a test of a problem with multiple threads

public class TestTaskSingletonRunnable implements Runnable{
    @Override
    public void run(a) {
        LazySingletonPattern instance = LazySingletonPattern.getInstance();
        System.out.println(Thread.currentThread().getName() + ":"+ instance); }}Copy the code

This implementation gets instances created in singleton mode in the Run method in the Runnable interface

public class TestSingletonPattern {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new TestTaskSingletonRunnable());
        Thread thread2 = new Thread(newTestTaskSingletonRunnable()); thread1.start(); thread2.start(); }}Copy the code

You need to start two threads to see if you’re getting the same instance

I’ve executed this code twice, once for the same instance and once for a different one

But one thing to note here is that the same instance doesn’t have to be created once, it could be created twice

The running results are as follows:

  • Same result
    • Normal running order execution
    • The object created the second time overwrites the object created the first time before printing the first time
  • Different results
    • If conditions are also applied and returned in the order in which they were executed

The first case (normal running order execution) will not say, this is also very easy to understand, after the execution of thread one, print finished, and then execute the second thread, the second case and the third case to talk about

Talk about the reasons for the error

The second created object overwrites the first created object before the first print

So let’s start by explaining this picture and talking about what causes this problem.

When the program starts, the code executes at line 10 of Figure 1, because I made a breakpoint there

See Figure 2: I’m executing the thread-0 Thread down to 11 lines of code because the instance variable is null.

Look at Figure 3: I switched to the thread-1 Thread. At this point, you can see that the if condition in line 10 is still true. In Figure 2, the if condition is still true because I didn’t make the code in Figure 2 create the object

Thread 0 and Thread 1 are now executed in 11 lines of code (if). Thread 0 and Thread 1 are now executed in 11 lines of code (if).

Look at Figure 5: We go back to the Thread-0 Thread and execute the code to line 13. I haven’t told the code to print the instance address yet. Switch to Thread-1 and execute to line 13. You will notice that the object created by Thread 0 will be replaced by the object created by Thread 1

That’s one reason it’s possible to print the same address in multithreaded mode, and another reason to do it sequentially (which is easier to understand than to say).

Two. Different results

Look at Figure 1: Line 10 when the program starts (this is thread-0)

See Figure 2: I let the Thread 0 code execute to line 11, at which point the object has not been created

Thread-1 has not created the object yet, so if is true. Thread-1 has not created the object yet.

See Figure 4: Executing thread-1 code up to line 11,

Look at Figure 5: Switch to Thread 0, click the small green button, it will automatically go to Thread 1 breakpoint, click the button again. You’ll notice that the two threads print results at different addresses

This is why not an instance is generated in a multithreaded environment

How to solve the problem of error in multi-threaded environment

To take a more crude approach, simply add the synchronized keyword to the getInstance() method

public class LazySingletonPattern {

    private static LazySingletonPattern instance;

    private LazySingletonPattern(a){}

    public synchronized static LazySingletonPattern getInstance(a){
        if(instance == null){
            instance = new LazySingletonPattern();
        }
        returninstance; }}Copy the code

Note: When using synchronized static methods, it is actually the class of the lock, also known as class lock

No matter how you run the code with the synchronized modifier (in a multi-threaded environment), you use the same instance, and you don’t get the two conditions above (the second object created overwrites the first object and a different result before the first print).

Why does synchronized get away with it? Why is this keyword so great?

Or can go through the breakpoint method to find out the cause

If thread-1 is in a MONITOR state, it will not continue executing until thread-1 is finished executing. In this case, it will not have the error described above In the case

However, with synchronized modification, a new problem arises that degrades system performance

Why would I say that? Well, let me just draw a picture of that so you can see, and then I’m going to add a new Thread, thread-2

If you look at the diagram, thread-1 and Thread-2 are blocked while thread0 is running inside the method, waiting for it to complete

Here’s an example

For example, in this circus, there is only one door, but the door is broken, the staff is repairing (this is like thread0 creating objects), and the other people (this is like other threads) have to wait behind.

This will affect the performance

Here’s another way to write it optimally: double-checked locks

public class LazyDoubleCheckSingletonPattern {

    private static volatile LazyDoubleCheckSingletonPattern instance;

    private LazyDoubleCheckSingletonPattern(a){}

    public LazyDoubleCheckSingletonPattern getInstance(a){
        if(instance == null) {synchronized (LazyDoubleCheckSingletonPattern.class){
                if(instance == null){
                    instance = newLazyDoubleCheckSingletonPattern(); }}}returninstance; }}Copy the code

This is the same method that uses synchronized, which blocks when a thread enters, but is better than synchronized, which filters out instances that do not need to be blocked (when a thread is acquiring an instance, the instance has already been created). I can go straight back

The role of two if judgments:

  • First the if
    • It is used to filter out situations that do not need to create objects (such as objects that have already been created).
  • Second if
    • Although it is whether the instance is empty, and the reason it is, in a multithreaded environment, if two threads are into the if it first, and then one of the threads created objects, when the second thread enters through the if whether this instance object is empty, if not null to create objects no longer, is to play such a role

It is best to use the volatile keyword here, where reordering of instructions is possible

Other ways of writing lazy: internal static classes
public class LazyStaticClassSingletonPattern {

    private LazyStaticClassSingletonPattern(a){}

    public LazyStaticClassSingletonPattern getInstance(a){
        return LazyHold.INSTANCE;
    }

    private static class LazyHold{
        private static LazyStaticClassSingletonPattern INSTANCE = newLazyStaticClassSingletonPattern(); }}Copy the code

The static inner class is only created when this class is used, so this is also lazy

The advantages and disadvantages

Advantages:

  • Can avoid wasting memory, high performance

Disadvantages:

  • Can be broken by reflection (not only but this can be broken by reflection, the other lazy ways can also be broken)
How is it destroyed by reflection
public class TestSingletonPattern {
    public static void main(String[] args) {
        try{ Class<? > clazz = LazyStaticClassSingletonPattern.class; Constructor<? > constructor = clazz.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            Object o1 = constructor.newInstance();
            System.out.println(o1);
        }catch(Exception e){ e.printStackTrace(); }}}Copy the code

In this way, objects can be created without using public methods. So what to do?

We can check in the constructor to see if the instance has been created and throw an exception if it has

public class LazyStaticClassSingletonPattern {

    private LazyStaticClassSingletonPattern(a){
        if(LazyHold.INSTANCE ! =null) {throw new RuntimeException("May not be created illegally."); }}public LazyStaticClassSingletonPattern getInstance(a){
        return LazyHold.INSTANCE;
    }

    private static class LazyHold{
        private static LazyStaticClassSingletonPattern INSTANCE = newLazyStaticClassSingletonPattern(); }}Copy the code

Why would you write that?

Since an instance of the static inner class has been created at class load time, this exception is thrown when the constructor determines that the condition is already true when the class is created by reflection

ThreadLocal singleton

public class ThreadLocatlSingletonPattern {

    private static final ThreadLocal<ThreadLocatlSingletonPattern> threadlocalInstance =
            new ThreadLocal<ThreadLocatlSingletonPattern>(){
                @Override
                protected ThreadLocatlSingletonPattern initialValue(a) {
                    return newThreadLocatlSingletonPattern(); }};private ThreadLocatlSingletonPattern(a){}

    public static ThreadLocatlSingletonPattern getInstance(a){
        returnthreadlocalInstance.get(); }}Copy the code

Note: this method is only a singleton in the same thread, if not in the same thread to obtain the object is not the same, this is sometimes used, only here, interested can see

Extension: How does serialization break the singleton pattern

public class TestSingletonPattern {
    public static void main(String[] args) {
        SingletonPatternOne s1 = null;
        SingletonPatternOne s2 = SingletonPatternOne.getInstance();

        FileOutputStream fos = null;

        try{
            fos = new FileOutputStream("SingletonPatternOne.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SingletonPatternOne.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SingletonPatternOne)ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);

        }catch (Exception e){

        }

    }
}
Copy the code

This method of destruction is achieved by means of serialization and deserialization.

How does this process work?

First serialization

It converts objects in memory to bytecode and saves them on hard disk

The data is then retrieved from the hard disk, loaded into memory via IO, and converted into a Java object

To avoid this problem, simply add the readResolve method to the singleton class, which returns the corresponding instance,

Here’s an example of hangry

public class SingletonPatternOne implements Serializable {

    private static final SingletonPatternOne instance;

    static {
        instance = new SingletonPatternOne();
    }

    private SingletonPatternOne(a) {}

    public static SingletonPatternOne getInstance(a){
        return instance;
    }

    private Object readResolve(a){
        returninstance; }}Copy the code