How many threads a JVM can handle?

Threads are expensive. They need lot of system resources, it’s time-consuming to create them and JVM is able to manage only few of them. Therefore we need asynchronous processing, new libraries, new languages etc. At least, that’s what I am hearing from everywhere. But is it really true? Are threads really so expensive? There is only one way how to find out. Just ask the machine.

The only thing we need to do is write a simple test. We just have to create as many sleeping or waiting threads as possible and we will see when it breaks. The reason why we are interested only in waiting threads is obvious. It has no sense to create thousands of active threads if we have only few CPUs. We want to simulate a situation, when threads are waiting for database data, incoming message or other CPU unrelated tasks.

public class CreateThreads {
	/**
	 * Thread class
	 * @author Lukas Krecan
	 *
	 */
	private static final class MyThread extends Thread
	{
		private final int number;
	
		public MyThread(int number) {
			this.number = number;
		}
		@Override
		public void run() {
			if (shouldPrintMessage(number))
			{
				System.out.println("Thread no. "+number+" started.");
			}
			try {
				//sleep forever
				Thread.sleep(Long.MAX_VALUE);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		long startTime = System.currentTimeMillis(); 
		final int noOfThreads = 1000000;
		int i=0;
		try {
			for (i=0; i < noOfThreads; i++)
			{
				if (shouldPrintMessage(i))
				{
					System.out.println("Creating thread "+i+" ("+(System.currentTimeMillis()-startTime)+"ms)");
				}
				new MyThread(i).start();
			}
		} catch (Throwable e) {
			System.out.println("Error thrown when creating thread "+i);
			e.printStackTrace();
		}
	}
	
	private static boolean shouldPrintMessage(int i)
	{
		return i % 100 == 0;
	}
}

As you can see, there is nothing special in the test. Every hundredth threads prints a message to console and all of them sleep forever. In the main method we are creating threads and when an exception is thrown, number of created threads is printed out.

Even though there is nothing special on the test, the results are really interesting and for me quite surprising. I will let you guess the results. The test is executed on my two years old laptop with Intel Core 2 Duo T8100 2.1GHz. There is 64bit Linux 2.6.31 and OpenJDK (IcedTea6 1.6.1) running on top of it.

Try to guess how many threads are created if I do not tinker with JVM arguments at all. Is it 1K? Or 10K? Or even more? Well, it's slightly more than 32K. Usually you see result like this

Creating thread 31900 (37656ms) 
Thread no. 31900 started. 
Creating thread 32000 (37736ms) 
Thread no. 32000 started. 
Creating thread 32100 (37818ms) 
Thread no. 32100 started. 
Error thrown when creating thread 32172 
java.lang.OutOfMemoryError: unable to create new native thread 
	at java.lang.Thread.start0(Native Method) 
	at java.lang.Thread.start(Thread.java:614) 
	at CreateThreads.main(CreateThreads.java:43) 

The question is, why we can not create more threads. Are we constrained by the heap size? Or maybe stack size? No in fact, 32K is apparently Linux kernel limit. Therefore there is nothing we can do in Java, Linux is just not able to handle more threads. But I think that 32K threads is not bad at all. Please keep in mind, that you will be able to reproduce the results only on modern operating systems and JVMs. I am afraid that Windows XP results will be much worse.

Please also note, that once the threads are created and all of them sleep, the application does not consume any CPU and it consumes around 800MB of memory. I am not sure why so much memory is used, it has to be investigated.

As we have seen, in this artificial scenario threads are relatively cheap. I do not want to say, that we should not use new libraries or tools. Most of them are quite useful. The only thing I want to say is that we should think twice before prematurely optimizing our applications. Sometimes the most straightforward and easiest approach is the best.

Next time, I will try to do some more real life example with servlet container.

Generovaný kód je zlo. A to i ve webových službách.

Minulý týden jsem zase trochu programoval, psal jsem jednu webovou službu. Ta měla WSDL definované třetí stranou, takže to byla poměrně jednoduchá a rutinní záležitost. Vzal jsem XML schema, z něj vygeneroval XmlBeans a začal jsem implementovat. Narazil jsem ale na jeden problém, který se mi nedařilo vyřešit.

Měl jsem vyrobit čtyři webové služby, jejichž odpovědi se lišily jen v pár detailech. V zásadě vypadaly takto:

<XXXResponse>
        <Version>1</m:Version>
        <RequestId>XYZ123</RequestId>
        <Result>SUCESS</Result>
        <XXXTransactionId>ABC456</XXXTransactionId>
</XXXResponse>

Místo XXX si představte různé fáze transakce. Můj problém spočíval v tom, že jsem nechtěl mít v programu čtyřikrát ten samý kus kódu pro generování odpovědi, chtěl jsem ho tam mít jen jednou. Jenže se to nedařilo, zkoušel jsem všelijaké finty s dědičností, vymýšlel jsem sofistikované wrappery, ale pořád jsem měl čtyři docela ošklivé bloky, které si byly ohromě podobné. Jediný rozdíl byl v jménu odpovědi a jménu transactionId. Ale tyto drobné rozdíly mi bránily v tom, udělat znovupoužitelný kód pro všechny webové služby.

Až mě napadla jednoduchá věc. Vždy jsem tvrdil, že generování kódu je zlo, kterému je potřeba se za každou cenu vyhnout. Ale u webových služeb jsem to tak nějak bral za dané. Znáte to, contract first přeci spočívá v tom, že začnu s návrhem XSD a podle něj píši kód. Je jen přirozené si ten kód pak nechat vygenerovat. A to je právě ta chyba. XML a Java jsou různé světy. Ano jsou si docela podobné, ale každý má jiný účel, jiné zvyklosti a jiné potřeby. Problém je, že, generovaný kód vždy odráží svůj původ.

Když generuji kód na základě XML, dostanu anemický model se kterým se mi pak špatně pracuje. Často pak skončím u toho, že mapuji vygenerované třídy na nějaké svoje vymazlené třídy a pak zase zpátky.

Když na druhou stranu generuji XSD na základě Javy, riskuji, že se s výsledným XML bude mým klientům špatně pracovat, že bude špatně rozšířitelné a podobně.

Je to podobný problém jako u objektově relačního mapování. Objekty a tabulky jsou si hrozně podobné, ale přesto je mezi nimi obrovská propast. Pamatuji si doby, kdy jsem generoval Javovské třídy na základě tabulek. Výsledek byl vždy žalostný. V poslední době jednoduše namapuji ručně psanou Javu na ručně optimalizovanou databázi a obě strany jsou spokojené.

Proč něco podobného nejde u XML? Pokud jste byli na mém lightning talku, možná si vzpomenete jak jsem si stěžoval, že v roce 2010 musím ručně mapovat XML třídy na normální třídy a zpátky. Mýlil jsem se, nemusím. Mohu udělat to samé jako s databází. Díky JAXB 2 mohu jednoduše namapovat svoje třídy na XML. Samozřejmě, v první iteraci si mohu pro jednoduchost ty třídy vygenerovat, ale pak už je ručně upravím a nikdy je negeneruji znovu.

A přesně tak jsem vyřešil svůj problém s duplicitou kódu. Ručně jsem si upravil JAXB třídy. Nejdřív jsem si vyrobil abstraktního předka všech odpovědí. To by ještě šlo zařídit úpravou XML schematu. Ale pak jsem do toho abstraktního předka přidal abstraktní metodu setTransactionId a dokonce jsem ho nechal implementovat nějaké rozhraní. To jsou koncepty, které v XML vůbec nemají smysl! Tím pádem ani nemá smysl pomýšlet na jejich generování.

Tím, že jsem udělal JAXB třídy nezávislé na XSD, jsem si zajistil, že je nemusím kopírovat do jiných interních tříd. Mohu svůj business kód udělat závislý jen na rozhraních, které XML třídy implementují. Konkrétně u mě to ušetřilo desítky řádků ošklivého kódu.

Kdybych měl dost odvahy, mohly bych dokonce i k těmto třídám přidat JPA anotace a ukládat je bez obav do databáze. Ale tak silný žaludek zatím ještě nemám.

Samozřejmě i tento přístup má své nevýhody. Asi největší je, že se mi může Java odchýlit od XML schematu. Může se mi stát, že udělám chybu, a začnu generovat nevalidní XML. Od toho ale máme testování, můžete použít třeba moji úžasnou knihovnu, která vám to pohlídá.

Zkuste se nad tím zamyslet. Generovaný kód nefungoval nikdy, vždy s ním byly jen problémy. Ale u webových služeb jsem ho alespoň já bral jako hotovou věc. Když se ho ale zbavíme, ušetříme si spoustu starostí. Náš kód může vypadat o něco víc objektově a my si užijeme mnohem víc legrace s programováním užitečných věcí.

Sláva abstrakci

Dnes mám nějakou náladu na filozofování, tak napíši něco, čeho už jsem se několikrát dotkl. Takže žádná novinka. Začnu svým oblíbeným prohlášením. Já píšu programy pro uživatele, snažím se řešit nějaký jejich problém. Říkejme tomu business problém. Rozhodně nepíši operační systémy, programovací jazyky, vývojová prostředí ani knihovny. Obvykle píši něco, co mému zákazníkovi přináší nějaké peníze.

Počítače ale pořád ještě pracují s nulami a jedničkami, od kterých je k penězům setsakra daleko. A od toho tu právě jsou abstrakce. Operační systém mě odstíní od těch nul a jedniček, virtuální stroj mě odstíní od operačního systému a správně zvolené knihovny mě odstíní od virtuálního stroje.

V ideálním případě bych měl psát kód, který co nejblíže popisuje řešení daného business problému. Pokud to tak není, je něco špatně. Pokud musím v kódu kopírovat pole bytů, starat se o vlákna, řešit systémové výjimky, starat se o paralelizaci, tak vím, že je někde chyba. Já chci prostě a jednoduše psát jenom business kód.

A nejsem to jenom já, podívejme se tímto pohledem na úspěšné technologie. Můžeme začít třeba samotnou Javou. Ta uspěla také díky tomu, že programátora odstínila od spousty do té doby běžných infrastrukturních starost. V Javě už jste se nemuseli starat o dealokování paměti, nemuseli jste řešit, jestli je String ukončený nulou nebo čím, nemuseli jste řešit jaký typ procesoru používáte nebo jaký je rozdíl mezi endianem a indiánem. V Javě jste se mohli jste se soustředit na psaní kódu. (Ano vím, že Smalltalk to uměl už o sto let dříve).

Podobně úspěšné je servlet API. To nám zas umožňuje chovat se k vícevláknovému systému tak, jako by v něm bylo jen jedno vlákno. Ano, jako každá abstrakce i tato prosakuje, ale v zásadě to tak je. Vsadil bych se, že tato podařená abstrakce stojí za úspěchem servlet API a tím pádem i enterprise Javy. Naopak špatnou abstrakci poznáte podle toho, že vás nutí dělat něco, co byste jinak nedělali. Třeba EJB 2 a jeho šílená rozhraní.

Nejen že mi dobrá abstrakce umožňuje se soustředit na můj business problém, ona mi i umožňuje držet krok s vývojem. Změní se operační systém? Nic se neděje. Změní se architektura procesoru? Nezájem, já jsem svůj program napsal v obecnější rovině, takže mě to (pravděpodobně) neovlivní. Naopak, já mohu využít novinek aniž bych na svůj kód třeba jen šáhnul. Procesor má tisíc jader? Nevadí, jenom změním konfiguraci serveru a mám vystaráno. Práce s více vlákny je pomalá? Nevadí, udělám update JVM a hned je vše jinak. Moje problémy za mě řeší někdo jiný.

A to je vlastně důvod, proč to všechno píši. Pokud mě někdo nutí zahrnout do programu něco, co nevyjadřuje business problém, vím že je to špatně. Pokud mě někdo například nutí používat asynchronní API a z mých požadavků to přímo nevyplývá, vím, že se snaží řešit problém na nesprávném místě. Často není vyhnutí, často není technologie dost pokročilá. Ale většinou je to prostě jen špatná volba.

Představme si, že například nechci, aby uživatel čekal, až se mi data zapíší do databáze, tak jak to píše Dagi. Samozřejmě, že to mohu řešit v kódu. Ale tam já to řešit nechci, tam já chci řešit jenom business. Musím se proto zamyslet jak to dělat jinak. Použít rychlejší databázi? Použít lepší JDBC ovladač? Použít databázi, která bere transakce velmi benevolentně? Všechno je možné, změna kódu je až poslední varianta.

Je tu ještě jeden z argumentů, který jsem zatím nezmínil. Lidé, kteří píší knihovny, databáze nebo virtuální stroje tomu rozumí mnohem lépe než já. Já vím jak převádět peníze, oni rozumí transakcím. Já vím co zajímá zákazníka, oni vědí jak synchronizovat data mezi vlákny. Kdykoliv to musím řešit za ně, vím, že buď odvedli špatnou práci nebo že já jsem si zvolil špatnou technologii.

Vemte si například ParallelArray. To mi umožňuje provádět operaci nad poli paralelně. Úžasná a i převratná knihovna. Ale z mého pohledu je špatná v tom, že mě nutí řešit thread pooly. To já nechci, to zákazníka vůbec nezajímá. Zamysleme se, proč to za mě nemůže řešit někdo jiný. Řekněme, že zavolám klasické Arrays.sort(). Proč nemůže běžet paralelně? Že JVM neví kolik vláken se má použít? Jak to že ne? To to ví dokonce lépe než já. Virtuální stroj dokonce i ví, kolik přesně má v daném okamžiku volných procesorů. To já nikdy v okamžiku psaní programu nevím a vědět nikdy nebudu. Že neví jestli je to operace k paralelizaci vhodná? Jak to? HotSpot to ví zase mnohem přesněji než já. To je stejné, jako kdybyste tvrdili, že je lepší, když programátor ručně uvolňuje paměť nebo optimalizuje kód.

Tak se nad tím prosím zkuste zamyslet. Třeba nad tím jak do toho všeho zapadají dynamické jazyky, DSL a podobně. Připadá mi, že to všechno dává docela smysl.