Full Analysis of Singleton mode (2) -- simple but confusing Singleton Mode

Source: Internet
Author: User
Document directory
  • Test Singleton Mode
  • Multi-thread considerations
  • Synchronization
  • A Method for Improving Performance
  • Double lock check
  • Implementation of an improved thread-safe Singleton Mode
Test Singleton Mode

Next, I use JUnit corresponding to log4j to test the singleton class, which runs through the rest of this article. If you are not familiar with JUnit or log4j, refer to related resources.

Example 2 is a singleton mode in JUnit test Example 1:
Example 2. Case study of a singleton Model

import org.apache.log4j.Logger;
import junit.framework.Assert;
import junit.framework.TestCase;

public class SingletonTest extends TestCase {
private ClassicSingleton sone = null, stwo = null;
private static Logger logger = Logger.getRootLogger();

public SingletonTest(String name) {
super(name);
}
public void setUp() {
logger.info("getting singleton...");
sone = ClassicSingleton.getInstance();
logger.info("...got singleton: " + sone);

logger.info("getting singleton...");
stwo = ClassicSingleton.getInstance();
logger.info("...got singleton: " + stwo);
}
public void testUnique() {
logger.info("checking singletons for equality");
Assert.assertEquals(true, sone == stwo);
}
}

Example 2 calls classicsingleton. getinstance () twice and stores the returned reference in the member variable. The testunique () method checks the references to see if they are the same. Example 3 is the output of this test case:
Example 3: output of this test case

Buildfile: build.xml

init:
[echo] Build 20030414 (14-04-2003 03:08)

compile:

run-test-text:
[java] .INFO main: [b]getting singleton...[/b]
[java] INFO main: [b]created singleton:[/b] Singleton@e86f41
[java] INFO main: ...got singleton: Singleton@e86f41
[java] INFO main: [b]getting singleton...[/b]
[java] INFO main: ...got singleton: Singleton@e86f41
[java] INFO main: checking singletons for equality

[java] Time: 0.032

[java] OK (1 test)

As shown in the previous list, the simple test of Example 2 passes smoothly ---- through classicsingleton. the references of the two Singleton classes obtained by getinstance () are indeed the same; however, you need to know that these references are obtained in a single thread. The following section focuses on the use of multithreading to test the singleton class.

Multi-thread considerations


In example 1, The classicsingleton. getinstance () method is thread-safe because of the following code:

1: if(instance == null) {
2: instance = new Singleton();
3: }

If a thread switches before the value assignment statement in the second line, the instance of the member variable is still null, And the other thread may enter the if block. In this case, two different Singleton instances are created. Unfortunately, this assumption rarely happens, and it is hard to appear during the test: in this case, the author may be amazed at the fact that this rarely happens, leading to the inability to perform tests, which can lead to increased vigilance ). To demonstrate this thread rotation, I have to implement the class in Example 1 again. Example 4 is the revised Singleton category:
Example 4: artificial arrangement

import org.apache.log4j.Logger;

public class Singleton {
private static Singleton singleton = null;
private static Logger logger = Logger.getRootLogger();
private static boolean firstThread = true;

protected Singleton() {
// Exists only to defeat instantiation.
}
public static Singleton getInstance() {
if(singleton == null) {
simulateRandomActivity();
singleton = new Singleton();
}
logger.info("created singleton: " + singleton);
return singleton;
}
private static void simulateRandomActivity() {
try {
if(firstThread) {
firstThread = false;
logger.info("sleeping...");

// This nap should give the second thread enough time
// to get by the first thread.
Thread.currentThread().sleep(50);
}
}
catch(InterruptedException ex) {
logger.warn("Sleep interrupted");
}
}
}

Except that the singleton class in this list forces a multi-threaded error handling, Example 4 is similar to the singleton class in Example 1. When the getinstance () method is called for the first time, the thread that calls this method will sleep for 50 milliseconds so that another thread can call getinstance () and create a new Singleton class instance. When the sleeping thread wakes up, it will also create a new Singleton class instance, so that we have two Singleton class instances. Although Example 4 is artificial, it simulates the real situation where the first thread calls getinstance () and is switched when it is not completed.
Example 5 test the singleton class of Example 4:
Example 5: failed tests

import org.apache.log4j.Logger;
import junit.framework.Assert;
import junit.framework.TestCase;

public class SingletonTest extends TestCase {
private static Logger logger = Logger.getRootLogger();
private static Singleton singleton = null;

public SingletonTest(String name) {
super(name);
}
public void setUp() {
singleton = null;
}
public void testUnique() throws InterruptedException {
// Both threads call Singleton.getInstance().
Thread threadOne = new Thread(new SingletonTestRunnable()),
threadTwo = new Thread(new SingletonTestRunnable());

threadOne.start();
threadTwo.start();

threadOne.join();
threadTwo.join();
}
private static class SingletonTestRunnable implements Runnable {
public void run() {
// Get a reference to the singleton.
Singleton s = Singleton.getInstance();

// Protect singleton member variable from
// multithreaded access.
synchronized(SingletonTest.class) {
if(singleton == null) // If local reference is null...
singleton = s; // ...set it to the singleton
}
// Local reference must be equal to the one and
// only instance of Singleton; otherwise, we have two
// Singleton instances.
Assert.assertEquals(true, s == singleton);
}
}
}

In the test case of Example 5, two threads are created and started respectively, waiting for completion. This case maintains a static reference to the singleton class, and each thread calls singleton. getinstance (). If this static member variable is not set, the first thread sets it as a reference by calling getinstance, the static variable is then compared with a local variable to determine whether it is equal.
In this test case, a series of things will occur: The first thread calls getinstance (), enters the if block, and then sleep; then, the second thread also calls getinstance () A singleton instance is created. The second thread sets this static member variable as the reference it creates. The second thread checks whether the static member variable is equal to a local backup. Then pass the test. When the first thread wakes up, it will also create an instance of the singleton class, and it will not set the static member variable (because the second thread has already been set ), so the static variable is out of sync with the local variable, and the equality test will fail. Example 6 lists the output of Example 5:
Example 6. Output in Example 5

Buildfile: build.xml
init:
[echo] Build 20030414 (14-04-2003 03:06)
compile:
run-test-text:
INFO Thread-1: sleeping...
INFO Thread-2: created singleton: Singleton@7e5cbd
INFO Thread-1: created singleton: Singleton@704ebb
junit.framework.AssertionFailedError: expected: but was:
at junit.framework.Assert.fail(Assert.java:47)
at junit.framework.Assert.failNotEquals(Assert.java:282)
at junit.framework.Assert.assertEquals(Assert.java:64)
at junit.framework.Assert.assertEquals(Assert.java:149)
at junit.framework.Assert.assertEquals(Assert.java:155)
at SingletonTest$SingletonTestRunnable.run(Unknown Source)
at java.lang.Thread.run(Thread.java:554)
[java] .
[java] Time: 0.577

[java] OK (1 test)

So far, we have known that example 4 is not thread-safe, so let's see how to fix it.

Synchronization


It is easy to make the singleton class of Example 4 thread-safe-as long as the getinstance () method is synchronized as follows:

public synchronized static Singleton getInstance() {
if(singleton == null) {
simulateRandomActivity();
singleton = new Singleton();
}
logger.info("created singleton: " + singleton);
return singleton;
}

After synchronizing the getinstance () method, we can get the following results returned by the test case in Example 5:


Buildfile: build.xml

init:
[echo] Build 20030414 (14-04-2003 03:15)

compile:
[javac] Compiling 2 source files

run-test-text:
INFO Thread-1: sleeping...
INFO Thread-1: created singleton: Singleton@ef577d
INFO Thread-2: created singleton: Singleton@ef577d
[java] .
[java] Time: 0.513

[java] OK (1 test)

In this case, the test case works normally and the troubles of multithreading are also solved. However, the clever reader may realize that the getinstance () method only needs to be synchronized when it is called for the first time. Because the synchronization performance is expensive (the synchronization method can be reduced to about 100 times than the non-synchronous method), we may introduce a performance improvement method, it only synchronizes the value assignment statement in the getinstance () method of the singleton class.

A Method for Improving Performance


When looking for a performance improvement method, you may choose to rewrite the getinstance () method as follows:


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

This code snippet only synchronizes key code, rather than the entire method. However, this code is not thread-safe. Consider the following assumption: thread 1 enters the synchronization block and thread 1 is switched before it assigns a value to the singleton member variable. Then another thread enters the if block. The second thread will wait until the first thread completes and still get two different Singleton instances. Is there any way to fix this problem? Read it.

Double lock check


At first glance, the double lock check seems to be a technology that makes lazy instantiation a thread safe. The following code snippet demonstrates this technology:

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

What happens if two threads access the getinstance () method at the same time? Imagine that the synchronization block of thread 1 is immediately switched. Next, the second thread enters the if block. When thread 1 exits the synchronization block, thread 2 checks again to see if the singleton instance is still null. Because the singleton member variable is set for thread 1, the second check of thread 2 will fail, and the second Singleton instance will not be created. That seems to be the case.
Unfortunately, the double lock check does not guarantee normal operation, because the compiler will assign a value to singleton randomly before the singleton constructor method is called. If thread 1 is switched after the singleton reference is assigned and before initialization, thread 2 will be returned with a reference to the uninitialized Singleton class instance.

Implementation of an improved thread-safe Singleton Mode


Example 7 lists a simple, fast, and thread-safe Singleton mode implementation:
Example 7. A simple Singleton class

public class Singleton {
public final static Singleton INSTANCE = new Singleton();
private Singleton() {
// Exists only to defeat instantiation.
}
}

This code is thread-safe because static member variables will be created when the class is accessed for the first time. You get a thread-safe implementation that automatically uses lazy instantiation; you should use it like this:

      Singleton singleton = Singleton.INSTANCE;
singleton.dothis();
singleton.dothat();
...

Of course everything is not perfect. Singleton is just a compromise. If you use that implementation, you cannot change it so that you may want to allow instances of multiple Singleton classes. You can change this method to return a unique instance or one of several hundred instances. you cannot use a public and static member variable to do so.

You can safely use the singleton mode of Example 7 or implement the getinstance () method with synchronization in Example 1. however, we must study another problem: You must specify this Singleton class during the compilation period, which is not very flexible. the registry of a singleton class allows us to specify a singleton class at runtime.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.