<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Dariusz Ludera Homepage &#187; singleton</title>
	<atom:link href="http://ludera.info/tag/singleton/feed" rel="self" type="application/rss+xml" />
	<link>http://ludera.info</link>
	<description>Dariusz Ludera oficjalna strona. Programista Java i JEE. Dariusz Ludera Homepage. Java and JEE developer.</description>
	<lastBuildDate>Sun, 27 Feb 2011 01:18:11 +0000</lastBuildDate>
	<language>pl</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=abc</generator>
		<item>
		<title>Singleton z double-checked locking oraz problem z wielowątkowością w TestNG</title>
		<link>http://ludera.info/java/singleton-z-double-checked-locking-oraz-problem-z-wielowatkowoscia-w-testng</link>
		<comments>http://ludera.info/java/singleton-z-double-checked-locking-oraz-problem-z-wielowatkowoscia-w-testng#comments</comments>
		<pubDate>Mon, 15 Mar 2010 21:39:00 +0000</pubDate>
		<dc:creator>Dariusz Ludera</dc:creator>
				<category><![CDATA[java]]></category>
		<category><![CDATA[double-checked locking]]></category>
		<category><![CDATA[eclipse]]></category>
		<category><![CDATA[maven]]></category>
		<category><![CDATA[singleton]]></category>
		<category><![CDATA[testng]]></category>

		<guid isPermaLink="false">http://ludera.info/?p=438</guid>
		<description><![CDATA[Wyczytałem ostatnio w mądrych książkach (tej i tej), że synchronizacja singletonów na poziomie całej metody getInstance może w środowisku wielowątkowym znacznie (25%) spowolnić pobieranie instancji obiektu przechowywanego przez owy singleton. W polskiej wikipedii ktoś napisał, że synchronizacja ta może obniżyć wydajność (w stosunku do metody niesynchronizowanej) o czynnik 100 lub więcej &#8211; szczerze, nie rozumiem [...]]]></description>
			<content:encoded><![CDATA[<p>Wyczytałem ostatnio w mądrych książkach (<a title="Effective Java Second Edition" href="http://java.sun.com/docs/books/effective/" target="_blank" onclick="pageTracker._trackPageview('/outgoing/java.sun.com/docs/books/effective/?referer=');">tej</a> i <a title="Head First Design Patterns" href="http://helion.pl/ksiazki/hfdepa.htm" target="_blank" onclick="pageTracker._trackPageview('/outgoing/helion.pl/ksiazki/hfdepa.htm?referer=');">tej</a>), że synchronizacja singletonów na poziomie całej metody getInstance może w środowisku wielowątkowym znacznie (25%) spowolnić pobieranie instancji obiektu przechowywanego przez owy singleton. W polskiej <a href="http://pl.wikipedia.org/wiki/Blokada_z_podw%C3%B3jnym_zatwierdzeniem_%28wzorzec_projektowy%29" target="_blank" onclick="pageTracker._trackPageview('/outgoing/pl.wikipedia.org/wiki/Blokada_z_podw_C3_B3jnym_zatwierdzeniem_28wzorzec_projektowy_29?referer=');">wikipedii</a> ktoś napisał, że synchronizacja ta może obniżyć wydajność (w stosunku do metody niesynchronizowanej) o czynnik 100 lub więcej &#8211; szczerze, nie rozumiem co oznacza rzeczony &#8216;czynnik 100&#8242;&#8230;</p>
<p>W przypadku, gdy wydajność ma dla nas kluczowe znaczenie, do tworzenia singletonów zalecano stosowanie wzorca double-checked locking (blokada z podwójnym zatwierdzaniem). Postanowiłem więc sprawdzić doświadczalnie jak w rzeczywistości wygląda spadek wydajności metod synchronizowanych. Napisałem więc trzy singletony:</p>
<p>tradycyjny</p>
<pre class="brush: java;">package info.ludera.SingletonPerformance.action.impl;

import info.ludera.SingletonPerformance.action.TestSingleton;

/**
 * Singleton z ZAWSZE synchronizowaną metodą {@link SimpleSingleton#getInstance()}
 * @author darekl
 *
 */
public class SimpleSingleton extends TestSingleton {

	/**
	 * Jedyna instancja obiektu {@link SimpleSingleton}
	 */
	private static SimpleSingleton instance;

	/**
	 * Konstruktor domyślny
	 */
	private SimpleSingleton() {
		super();
	}

	/**
	 * Zwraca instancję obiektu  {@link SimpleSingleton}
	 * @return
	 */
	public static synchronized SimpleSingleton getInstance() {

		if (instance == null) {
			instance = new SimpleSingleton();
		}
		return instance;
	}
}
</pre>
<p>i dwie różne implementacje double-checked locking:</p>
<p>&#8220;podstawowa&#8221;</p>
<pre class="brush: java;">package info.ludera.SingletonPerformance.action.impl;

import info.ludera.SingletonPerformance.action.TestSingleton;

/**
 * Singleton z synchronizowaną metodą {@link SimpleSingleton#getInstance()}. Synchronizacja aktywna jest tylko przy pierwszym uruchomieniu tej metody.
 * @author darekl
 *
 */
public class ThreadSaveSingleton extends TestSingleton {

	/**
	 * Jedyna instancja obiektu {@link SimpleSingleton}
	 */
	private volatile static ThreadSaveSingleton instance = null;

	/**
	 * Konstruktor domyślny
	 */
	private ThreadSaveSingleton() {
		super();
	}

	/**
	 * Zwraca instancję obiektu  {@link SimpleSingleton}
	 * @return
	 */
	public static ThreadSaveSingleton getInstance() {

		if (instance == null) {
			synchronized (ThreadSaveSingleton.class) {
				if (instance == null) {
					instance = new ThreadSaveSingleton();
				}
			}
		}
		return instance;
	}
}
</pre>
<p>i &#8220;<a href="http://technology.amis.nl/blog/4384/the-double-checked-locking-confusion" target="_blank" onclick="pageTracker._trackPageview('/outgoing/technology.amis.nl/blog/4384/the-double-checked-locking-confusion?referer=');">rozszerzona</a>&#8221;</p>
<pre class="brush: java;">package info.ludera.SingletonPerformance.action.impl;

import info.ludera.SingletonPerformance.action.TestSingleton;

/**
 * Singleton z synchronizowaną metodą {@link SimpleSingleton#getInstance()}. Synchronizacja aktywna jest tylko przy pierwszym uruchomieniu tej metody.
 * @author darekl
 *
 */
public class AdvancedThreadSaveSingleton extends TestSingleton {

	/**
	 * Jedyna instancja obiektu {@link SimpleSingleton}
	 */
	private volatile static AdvancedThreadSaveSingleton instance = null;

	/**
	 * Konstruktor domyślny
	 */
	private AdvancedThreadSaveSingleton() {
		super();
	}

	/**
	 * Zwraca instancję obiektu  {@link SimpleSingleton}
	 * @return
	 */
	public static AdvancedThreadSaveSingleton getInstance() {

		AdvancedThreadSaveSingleton result = instance;
		if (result == null) {
			synchronized (AdvancedThreadSaveSingleton.class) {
				result = instance;
				if (result == null) {
					instance = result = new AdvancedThreadSaveSingleton();
				}
			}
		}
		return result;
	}
}
</pre>
<p>Przetestowałem ich działanie poniższym testem dla różnej ilości wątków (100-100000):</p>
<pre class="brush: java;">package info.ludera.SingletonPerformance.test;

...

public class FabricMultiThreadSingletonTest extends AbstractSingletonTest {

	@DataProvider
	public Object[][] ValidJavaVersion() {

		return new Object[][]{
				{ 5 }
		};
	}

	/**
	 * Ilość iteracji
	 */
	protected static Long testCounter;

	/**
	 * Prefix nazwy wątku
	 */
	protected static String threadNamePrefix = &quot;Thread_&quot;;

	@BeforeClass
	@Parameters(value=&quot;repetitionQuantity&quot;)
	public void setTestCounter(long repetitionQuantity) {
		testCounter = repetitionQuantity;
	}

	@Test
	public void getSimpleSingletonNTimes() {

		for (long i=0; i&lt;testCounter; i++) {
			SingletonFactory simpleSingletonFactory = new SimpleSingletonFactory(threadNamePrefix + i);
			simpleSingletonFactory.start();
		}
	}

	@Test(dataProvider = &quot;ValidJavaVersion&quot;)
	public void getThreadSaveSingletonNTimes(final int javaVersion) {

		//Assert.assertTrue((Integer.parseInt(&quot;&quot; + System.getProperty(&quot;java.version&quot;).charAt(2)) &gt;= javaVersion), &quot;This test is avialable only with Java 5 or above!&quot;);

		for (long i=0; i&lt;testCounter; i++) {
			SingletonFactory threadSaveSingletonFactory = new ThreadSaveSingletonFactory(threadNamePrefix + i);
			threadSaveSingletonFactory.start();
		}
	}

	@Test(dataProvider = &quot;ValidJavaVersion&quot;)
	public void getAdvencedThreadSaveSingletonNTimes(final int javaVersion) {

		//Assert.assertTrue((Integer.parseInt(&quot;&quot; + System.getProperty(&quot;java.version&quot;).charAt(2)) &gt;= javaVersion), &quot;This test is avialable only with Java 5 or above!&quot;);

		for (long i=0; i&lt;testCounter; i++) {
			SingletonFactory advancedThreadSaveSingletonFactory = new AdvancedThreadSaveSingletonFactory(threadNamePrefix + i);
			advancedThreadSaveSingletonFactory.start();
		}
	}
}
</pre>
<p>I rzeczywiście. Czasowe wyniki działania tych testów, dla różnej ilości iteracji, rozchodziły się w słuszną stronę. Wraz ze wzrostem iteracji, SimpleSingleton działał coraz wolniej w stosunku do wersji z double-checked locking. Testy przeprowadzałem na JDK 1.6.0_16 oraz JDK 1.5.0_22. Nie testowałem wydajności na Java 1.4. Istnieje bowiem prawdopodobieństwo wystąpienia problemu błędnej implementacji volatile. Oto przykładowe wyniki dla 10000 iteracji na JDK 1.6:</p>
<pre>getSimpleSingletonNTimes - 1624ms
getThreadSaveSingletonNTimes - 1209ms
getAdvencedThreadSaveSingletonNTimes - 1153ms</pre>
<p>To nie wszystko. Po zastanowieniu, doszedłem do wniosku, że użycie wzorca fabryki do  takich teścików to przerost formy nad treścią. Stwierdziłem więc, że wykorzystam wielowątkowości z TestNG.</p>
<p>oto test:</p>
<pre class="brush: java;">package info.ludera.SingletonPerformance.test;

...

import org.testng.annotations.Test;

/**
 * Test wydajności synchronizacji singletonów {@link info.ludera.SingletonPerformance.test.helper.SimpleSingletonFactory} i {@link info.ludera.SingletonPerformance.test.helper.ThreadSaveSingletonFactory}
 * @author darekl
 *
 */
public class MultiThreadSingletonTest extends AbstractSingletonTest {

	@Test(threadPoolSize=1000, invocationCount = 1000)
	public void getSimpleSingleton() {

		SimpleSingleton.getInstance();
	}

	@Test(dataProvider = &quot;ValidJavaVersion&quot;, threadPoolSize=1000, invocationCount = 1000)
	public void getThreadSaveSingleton(final int javaVersion) {

		//Assert.assertTrue((Integer.parseInt(&quot;&quot; + System.getProperty(&quot;java.version&quot;).charAt(2)) &gt;= javaVersion), &quot;This test is avialable only with Java 5 or above!&quot;);

		ThreadSaveSingleton.getInstance();
	}

	@Test(dataProvider = &quot;ValidJavaVersion&quot;, threadPoolSize=1000, invocationCount = 1000)
	public void getAdvencedThreadSaveSingleton(final int javaVersion) {

		//Assert.assertTrue((Integer.parseInt(&quot;&quot; + System.getProperty(&quot;java.version&quot;).charAt(2)) &gt;= javaVersion), &quot;This test is avialable only with Java 5 or above!&quot;);

		AdvancedThreadSaveSingleton.getInstance();
	}
}
</pre>
<p>i wyniki (tym razem dla 1000 wątków):</p>
<pre>getSimpleSingletonNTimes - 110ms
getThreadSaveSingletonNTimes - 97ms
getAdvencedThreadSaveSingletonNTimes - 100ms
getSimpleSingleton - 716ms
getThreadSaveSingleton - 2050ms
getAdvencedThreadSaveSingleton - 2079ms</pre>
<p>Zastanawiające. Dlaczego trzy ostatnie testy (te dopisane powyżej) nie układają się tak jak te poprzednie, gdzie wielowątkowość napisałem ręcznie? Dlaczego widać tak duże różnice w wydajności? Przyznam szczerze, że z wielowątkowości w TestNG korzystam po praz pierwszy, czy ktoś bardziej doświadczony w tym temacie, mógłby mi wyjaśnić w czym tkwi problem na który się natknąłem i co robię źle?</p>
<p><a title="SingletonPerformance.zip" href="http://ludera.info/files/SingletonPerformance.zip">Pobierz</a> projekt eclipse (wymagania: Maven2 lub Eclipse z pluginami: <a title="m2eclipse" href="http://m2eclipse.sonatype.org/" target="_blank" onclick="pageTracker._trackPageview('/outgoing/m2eclipse.sonatype.org/?referer=');">m2eclipse</a> i <a title="TestNG Eclipse Plugin" href="http://testng.org/doc/index.html#locations-projects" target="_blank" onclick="pageTracker._trackPageview('/outgoing/testng.org/doc/index.html_locations-projects?referer=');">TestNG</a>)</p>
<p>PS. Apropos optymalizacji kodu pod względem jego wydajności, polecam <a title="Dlaczego moje optymalizacje nie optymalizują? " href="http://www.devblogi.pl/2010/03/dlaczego-moje-optymalizacje-nie.html" onclick="pageTracker._trackPageview('/outgoing/www.devblogi.pl/2010/03/dlaczego-moje-optymalizacje-nie.html?referer=');">ten</a> artykuł. Po jego przeczytaniu nasunęła mi się analogia dotycząca RDBMS i hintów oraz przesiadki z optymalizatorów regułowych na kosztowe <img src='http://ludera.info/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow: hidden;">
<p>package info.ludera.SingletonPerformance.action.impl;</p>
<p>import info.ludera.SingletonPerformance.action.TestSingleton;</p>
<p>/**<br />
* Singleton z ZAWSZE synchronizowaną metodą {@link SimpleSingleton#getInstance()}<br />
* @author darekl<br />
*<br />
*/<br />
public class SimpleSingleton extends TestSingleton {</p>
<p>/**<br />
* Jedyna instancja obiektu {@link SimpleSingleton}<br />
*/<br />
private static SimpleSingleton instance;</p>
<p>/**<br />
* Konstruktor domyślny<br />
*/<br />
private SimpleSingleton() {<br />
super();<br />
}</p>
<p>/**<br />
* Zwraca instancję obiektu  {@link SimpleSingleton}<br />
* @return<br />
*/<br />
public static synchronized SimpleSingleton getInstance() {</p>
<p>//System.out.println(&#8220;SimpleSingleton.getInstance start for &#8221; + Thread.currentThread().getName());<br />
if (instance == null) {<br />
instance = new SimpleSingleton();<br />
}<br />
//System.out.println(&#8220;SimpleSingleton.getInstance stop  for &#8221; + Thread.currentThread().getName());<br />
return instance;<br />
}<br />
}</p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://ludera.info/java/singleton-z-double-checked-locking-oraz-problem-z-wielowatkowoscia-w-testng/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

