分享
 
 
 

Simply Singleton -- part2 By David Geary

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

Test singletons

Throughout the rest of this article, I use JUnit in concert with log4j to test singleton classes. If you are not familiar with JUnit or log4j, see Resources.

Example 2 lists a JUnit test case that tests Example 1's singleton:

Example 2. A singleton test case

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's test case invokes ClassicSingleton.getInstance() twice and stores the returned references in member variables. The testUnique() method checks to see that the references are identical. Example 3 shows that test case output:

Example 3. Test case output

Buildfile: build.xml

init:

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

compile:

run-test-text:

[java] .INFO main: getting singleton...

[java] INFO main: created singleton: Singleton@e86f41

[java] INFO main: ...got singleton: Singleton@e86f41

[java] INFO main: getting singleton...

[java] INFO main: ...got singleton: Singleton@e86f41

[java] INFO main: checking singletons for equality

[java] Time: 0.032

[java] OK (1 test)

As the preceding listing illustrates, Example 2's simple test passes with flying colors—the two singleton references obtained with ClassicSingleton.getInstance() are indeed identical; however, those references were obtained in a single thread. The next section stress-tests our singleton class with multiple threads.

Multithreading considerations

Example 1's ClassicSingleton.getInstance() method is not thread-safe because of the following code:

1: if(instance == null) {

2: instance = new Singleton();

3: }

If a thread is preempted at Line 2 before the assignment is made, the instance member variable will still be null, and another thread can subsequently enter the if block. In that case, two distinct singleton instances will be created. Unfortunately, that scenario rarely occurs and is therefore difficult to produce during testing. To illustrate this thread Russian roulette, I've forced the issue by reimplementing Example 1's class. Example 4 shows the revised singleton class:

Example 4. Stack the deck

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");

}

}

}

Example 4's singleton resembles Example 1's class, except the singleton in the preceding listing stacks the deck to force a multithreading error. The first time the getInstance() method is called, the thread that invoked the method sleeps for 50 milliseconds, which gives another thread time to call getInstance() and create a new singleton instance. When the sleeping thread awakes, it also creates a new singleton instance, and we have two singleton instances. Although Example 4's class is contrived, it stimulates the real-world situation where the first thread that calls getInstance() gets preempted.

Example 5 tests Example 4's singleton:

Example 5. A test that fails

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);

}

}

}

Example 5's test case creates two threads, starts each one, and waits for them to finish. The test case maintains a static reference to a singleton instance, and each thread calls Singleton.getInstance(). If the static member variable has not been set, the first thread sets it to the singleton obtained with the call to getInstance(), and the static member variable is compared to the local variable for equality.

Here's what happens when the test case runs: The first thread calls getInstance(), enters the if block, and sleeps. Subsequently, the second thread also calls getInstance() and creates a singleton instance. The second thread then sets the static member variable to the instance it created. The second thread checks the static member variable and the local copy for equality, and the test passes. When the first thread awakes, it also creates a singleton instance, but that thread does not set the static member variable (because the second thread has already set it), so the static variable and the local variable are out of synch, and the test for equality fails. Example 6 lists Example 5's test case output:

Example 6. Example 5's output

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)

Now that we know Example 4's singleton is not thread-safe, let's see how we can fix it.

Synchronization

Making Example 4's singleton class thread-safe is easy—just synchronize the getInstance() method like this:

public synchronized static Singleton getInstance() {

if(singleton == null) {

simulateRandomActivity();

singleton = new Singleton();

}

logger.info("created singleton: " + singleton);

return singleton;

}

After we synchronize the getInstance() method, we can rerun Example 5's test case with the following results:

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)

This time, the test case works and our multithreading worries are over; however, the astute reader may realize that the getInstance() method only needs to be synchronized the first time it is called. Because synchronization is very expensive performance-wise (synchronized methods can run up to 100 times slower than unsynchronized methods), perhaps we can introduce a performance enhancement that only synchronizes the singleton assignment in getInstance().

A performance enhancement

In search of a performance enhancement, you might choose to rewrite the getInstance() method like this:

public static Singleton getInstance() {

if(singleton == null) {

synchronized(Singleton.class) {

singleton = new Singleton();

}

}

return singleton;

}

Instead of synchronizing the entire method, the preceding code fragment only synchronizes the critical code. However, the preceding code fragment is not thread-safe. Consider the following scenario: Thread 1 enters the synchronized block, and, before it can assign the singleton member variable, the thread is preempted. Subsequently, another thread can enter the if block. The second thread will wait for the first thread to finish, but we will still wind up with two distinct singleton instances. Is there a way to fix this problem? Read on.

Double-checked locking

Double-checked locking is a technique that, at first glance, appears to make lazy instantiation thread-safe. That technique is illustrated in the following code fragment:

public static Singleton getInstance() {

if(singleton == null) {

synchronized(Singleton.class) {

if(singleton == null) {

singleton = new Singleton();

}

}

}

return singleton;

}

What happens if two threads simultaneously access getInstance()? Imagine Thread 1 enters the synchronized block and is preempted. Subsequently, a second thread enters the if block. When Thread 1 exits the synchronized block, Thread 2 makes a second check to see if the singleton instance is still null. Since Thread 1 set the singleton member variable, Thread 2's second check will fail, and a second singleton will not be created. Or so it seems.

Unfortunately, double-checked locking is not guaranteed to work because the compiler is free to assign a value to the singleton member variable before the singleton's constructor is called. If that happens, Thread 1 can be preempted after the singleton reference has been assigned, but before the singleton is initialized, so Thread 2 can return a reference to an uninitialized singleton instance.

Since double-checked locking is not guaranteed to work, you must synchronize the entire getInstance() method. However, another alternative is simple, fast, and thread-safe.

An alternative thread-safe singleton implementation

Example 7 lists a simple, fast, and thread-safe singleton implementation:

Example 7. A simple singleton

public class Singleton {

public final static Singleton INSTANCE = new Singleton();

private Singleton() {

// Exists only to defeat instantiation.

}

}

The preceding singleton implementation is thread-safe because static member variables created when declared are guaranteed to be created the first time they are accessed. You get a thread-safe implementation that automatically employs lazy instantiation; here's how you use it:

Singleton singleton = Singleton.INSTANCE;

singleton.dothis();

singleton.dothat();

...

Of course, like nearly everything else, the preceding singleton is a compromise; if you use that implementation, you can't change your mind and allow multiple singleton instances later on. With a more conservative singleton implementation, instances are obtained through a getInstance() method, and you can change those methods to return a unique instance or one of hundreds. You can't do the same with a public static member variable.

You can safely use Example 7's singleton implementation or Example 1's implementation with a synchronized getInstance() method. However, we must explore another issue: You must specify the singleton class at compile time, which is not very flexible. A registry of singletons will let us specify singleton classes at runtime.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有