Programovací jazyk Clojure 7: další podpůrné prostředky pro paralelní programování

Pavel Tišnovský 24. 7. 2012

V dnešní části seriálu o programovacím jazyce Java dokončíme popis objektů typu „futures“ nabízených programovacím jazykem Clojure. Kromě toho si řekneme, jak lze využít objekty typu „promise“, a navíc se budeme zabývat velmi zajímavou technologií – STM neboli Software Transactional Memory.

Obsah

1. Malé zopakování z minula – objekty typu „futures“

2. Další funkce a makra používaná při práci s objekty typu „futures“

3. Objekty typu „promise“

4. Nebezpečí, které hrozí při práci s objekty typu „promise“

5. Architektura dataflow realizovaná s využitím objektů typu „promise“

6. Technologie STM – Software Transactional Memory

7. Funkce a makra používaná při práci s transakcemi

8. Demonstrační příklad: transakce při převodu peněz mezi dvěma účty

9. Odkazy na Internetu

1. Malé zopakování z minula – objekty typu „futures“

V předchozí části seriálu o Javě i o vlastnostech virtuálního stroje Javy jsme si mj. popsali i takzvané objekty typu futures, které představují jednu z několika technologií programovacího jazyka Clojure vyvinutých pro snadnou tvorbu vícevláknových aplikací. Připomeňme si, že s využitím objektů typu futures je možné spustit nějaký výpočet (obecně se jedná o vyhodnocení funkce, zavolání makra či o zavolání speciální formy), který je prováděn asynchronně v samostatném vlákně, tj. tak, že hlavní vlákno může ihned pokračovat v provádění dalších příkazů a nemusí čekat na dokončení tohoto výpočtu. V případě, že asynchronní výpočet skončí dřív, než je zapotřebí znát jeho výsledek, nemusí vůbec dojít k synchronizaci vláken, ovšem ve chvíli, kdy se programátor dotazuje na výsledek asynchronního výpočtu, který ještě není dokončen (tj. při čtení výsledku s použitím funkce deref nebo makra @), je hlavní vlákno programu pozastaveno tak, aby se počkalo na dokončení výpočtu.

Díky tomuto chování je práce s objekty typu futures velmi snadná a bezpečná (tj. při správné metodě programování by nemělo dojít k deadlockům, „vyhladovění/starvation“ vláken atd.). Jednoduchý program spouštějící výpočty v asynchronně běžících samostatných vláknech může vypadat například takto (podrobnější popis viz předchozí část seriálu):

; funkce pro velmi pomalý výpočet členů Fibonacciho řady
(defn fibonacci
    [n]
    (if (> n 2)
        n
        (+ (fibonacci (- n 2)) (fibonacci (- n 1)))))
 
 
; namísto přímého volání funkce fibonacci však spustíme
; dva asynchronní výpočty s využitím (future funkce)
 
user=> (def future_fibonacci1 (future (fibonacci 35)))
#'user/future_fibonacci1
 
user=> (def future_fibonacci2 (future (fibonacci 35)))
#'user/future_fibonacci2
 
; oba výpočty jsou nyní prováděny ve svých vláknech na pozadí
; - lze se o tom přesvědčit například programem "top"
;   nebo s využitím JConsole/VisualVM spuštěné nad interpretrem
;   programovacího jazyka Clojure
 
; nyní si explicitně vyžádáme výsledek výpočtu
; - zde již může být hlavní vlákno pozastaveno,
;   aby mohl být výpočet dokončen
user=> @future_fibonacci1
9227465
 
user=> @future_fibonacci2
9227465

2. Další funkce a makra používaná při práci s objekty typu „futures“

V mnoha případech můžeme vystačit pouze s použitím makra future (viz též demonstrační příklad uvedený v předchozí kapitole) s tím, že pro získání výsledku výpočtu bude sloužit funkce deref, která se velmi často v programech zapisuje pouze pomocí zavináče @ (v tomto případě jde o makro zabudované do preprocesoru). Ovšem ve složitějších aplikacích je mnohdy nutné se explicitně dotázat na to, zda již asynchronní výpočet skončil. Tento dotaz by měl proběhnout bez toho, aby se hlavní vlákno pozastavilo, popř. taktéž mohou nastat situace, které si vynutí ukončení jednoho či více těchto výpočtů. Pro všechny podobné situace samozřejmě programovací jazyk Clojure nabízí odpovídající funkce či makra. Dotaz na stav výpočtu se provádí s využitím funkce realized?. Jak již z názvu této funkce (otazník na konci) vyplývá, jedná se o predikát vracející pravdivostní hodnotu true nebo false v závislosti na tom, jestli byl asynchronní výpočet představovaný objektem typu future již ukončen či nikoli.

Podívejme se nyní na demonstrační příklad, v němž se aktivně (tudíž nepříliš chytře :-) čeká na dokončení vlákna s výpočtem:

; rekurzivní zápis funkce pro výpočet jednoho členu
; Fibonacciho posloupnosti
(defn fibonacci
    [n]
    (if (> n 2)
        n
        (+ (fibonacci (- n 2)) (fibonacci (- n 1)))))
 
 
; pomocná funkce, která pozastaví aktivní
; vlákno na přibližně jednu sekundu
(defn wait-one-sec [] (Thread/sleep 1000))
 
; vytvoření future objektu s asynchronním výpočtem
(def future_fibonacci1 (future (fibonacci 40)))
 
; aktivní čekání na dokončení výpočtu
(while (not (realized? future_fibonacci1))
       (println "Cekam na konec vypoctu... ")
       (wait-one-sec)
)
 
; tisk výsledku výpočtu
(println "Vysledek: " @future_fibonacci1)

Podobným způsobem je možné použít i predikát future-done?. Ten je možné aplikovat pouze na objekty typu future, zatímco predikát realized? je možné aplikovat i na lazy sekvence atd.:

; vytvoření future objektu s asynchronním výpočtem
(def future_fibonacci1 (future (fibonacci 40)))
 
; aktivní čekání na dokončení výpočtu
(while (not (future-done? future_fibonacci1))
       (println "Cekam na konec vypoctu... ")
       (wait-one-sec)
)
 
; tisk výsledku výpočtu
(println "Vysledek: " @future_fibonacci1)

Asynchronní výpočet lze taktéž násilně ukončit funkcí future-cancel. Pokud k ukončení výpočtu skutečně dojde, vrátí tato funkce pravdivostní hodnotu true, v opačném případě (výpočet již byl ukončen) se vrátí pravdivostní hodnota false. Opět se podívejme na jednoduchý demonstrační příklad:

; vytvoření future objektu s asynchronním výpočtem
(def future_fibonacci1 (future (fibonacci 100)))
#'user/future_fibonacci1
 
; čekání jednu sekundu (výpočet by ještě neměl být ukončen)
(wait-one-sec)
nil
 
; násilné ukončení asynchronního výpočtu
(future-cancel future_fibonacci1)
true
 
; při druhém pokusu o násilné ukončení
; asynchronního výpočtu se pouze vrátí hodnota false
(future-cancel future_fibonacci1)
false

3. Objekty typu „promise“

S objekty typu future dosti úzce souvisí i funkce deliver a objekty typu promise, které mohou být použity pro uložení výsledku výpočtu do zvolené proměnné (stále se sice bavíme o výpočtu, ale může se samozřejmě jednat o jakoukoli dlouhotrvající operaci, například zavolání webové služby, čtení dat z SQL databáze atd. atd.). V programovacím jazyku Clojure představují objekty typu promise takové hodnoty, které ještě nemusí v době své deklarace vůbec existovat, tj. může se jednat o hodnoty, jež teprve vzniknou, typicky na základě asynchronně běžícího výpočtu (použití objektů typu promise v jednovláknovém programu je sice možné, ale většinou zbytečně komplikované a současně i nebezpečné). S těmito objekty lze provést pouze dvě činnosti – může se jim nastavit nějaká hodnota (a to pouze jedenkrát, nelze ji potom změnit) a může se přečíst, přesněji řečeno dereferencovat, jejich hodnota. Důležité je, že pokud tato hodnota ještě neexistuje (tj. asynchronní výpočet se ještě nedokončil), je vlákno, které hodnotu dereferencuje (čte) pozastaveno na tak dlouho, dokud není hodnota objektu typu promise skutečně nastavena. Ihned poté, co je hodnota nastavena, mohou k této hodnotě ihned přistupovat i všechna ostatní vlákna.

Práce s objekty typu promise je ve skutečnosti velmi jednoduchá. Nejprve se objekt promise vytvoří zavoláním funkce promise (která nemá žádné parametry) a samozřejmě se tento objekt může navázat na libovolný symbol s využitím speciální formy def, tj. takto: (def symbol (promise)). Hodnotu lze objektu typu promise přiřadit s využitím funkce deliver, které se předá jak symbol navázaný na objekt promise, tak i hodnota, jenž se má k objektu přiřadit: (deliver jméno-objektu-promise hodnota). Od této chvíle mohou tuto hodnotu používat všechna vlákna, která k ní mohou přistupovat buď s využitím funkce (deref jméno-objektu-promise) nebo zkráceně @jméno-objektu-promise (opět se zde setkáváme s makrem @ vytvořeným v preprocesoru). V následujícím demonstračním příkladu je vytvořen objekt typu promise, jenž je navázán na symbol promise-test. Následně je tomuto objektu přiřazena hodnota 42, která je následně vypsána. Všechny tyto činnosti se provádí v jediném vláknu, nedochází zde tedy k vytvoření samostatného vlákna pro „výpočet“:

; vytvoření objektu typu promise
; a navázání na symbol promise-test
(def promise-test (promise))
#'user/promise-test
 
; nastavení hodnoty
(deliver promise-test 42)
#<core$promise$reify__6153@1318b: 42>
 
; získání nastavené hodnoty
@promise-test
42

4. Nebezpečí, které hrozí při práci s objekty typu „promise“

Důležitý je taktéž již v předešlé kapitole zmíněný fakt, že hodnotu do objektu typu promise je možné nastavit pouze jedenkrát, tj. po nastavení ji již nelze změnit. Další pokusy o nastavení jiné hodnoty jsou jazykem Clojure ignorovány (v některých verzích se navíc při pokusu o přepis již nastavené hodnoty vyvolá výjimka – je nutné otestovat). Opět si to můžeme ukázat na jednoduchém demonstračním příkladu, kdy se do promise-test nejprve uloží hodnota 42 a poté se snažíme do stejného objektu typu promise uložit odlišnou hodnotu: 100. To se nepovede, takže výraz @promise-test je vyhodnocen na 42:

; vytvoření objektu typu promise
; a navázání na symbol promise-test
(def promise-test (promise))
#'user/promise-test
 
; nastavení hodnoty
(deliver promise-test 42)
#<core$promise$reify__6153@1318b: 42>
 
; pokus o nové nastavení hodnoty
(deliver promise-test 100)
nil
 
; získání nastavené hodnoty
@promise-test
42
; vidíme, že se vrátila prvně nastavená hodnota

Při práci s objekty typu promise hrozí jedno velké nebezpečí, s nímž jsme se vlastně při popisu vlastností programovacího jazyka Clojure ještě nesetkali – v případě, že se do tohoto objektu nepřiřadí žádná hodnota s využitím funkce deliver a některé vlákno se bude snažit přečíst hodnotu přiřazenou k objektu promise, nastane deadlock. Příklad deadlocku je v tomto případě až neskutečně jednoduchý, protože pouze postačuje vytvořit objekt typu promise a ihned poté se v tomtéž vlákně snažit přečíst k němu nastavenou hodnotu. To se samozřejmě nepovede, takže smyčka REPL bude v deadlocku čekat, jestli náhodou jiné vlákno přece jen kýženou hodnotu nenastaví:

; vytvoření objektu typu promise
; a navázání na symbol promise-test
(def promise-test (promise))
#'user/promise-test
 
; pokus o čtení nenastavené hodnoty
@promise-test
*** deadlock ***

V některých případech je nutné před čtením hodnoty přiřazené k objektu typu promise zjistit, zda je tomuto objektu hodnota již přiřazena. Zde nám pomůže známý predikát realized?, viz též následující demonstrační příklad:

; vytvoření objektu typu promise
; a navázání na symbol promise-test
(def promise-test (promise))
#'user/promise-test
 
; test, zda je hodnota již přiřazena
(realized? promise-test)
false
; ... samozřejmě ještě není
 
; nastavení hodnoty
(deliver promise-test 42)
#<core$promise$reify__6153@1318b: 42>
 
; další test, zda je hodnota již přiřazena
(realized? promise-test)
true
; ... nyní již jsme úspěšnější
 
@promise-test
42

5. Architektura dataflow realizovaná s využitím objektů typu „promise“

Po přečtení předchozích dvou kapitol by se možná mohlo zdát, že objekty typu promise přináší při tvorbě reálných aplikací jen problémy a neměly by se vlastně vůbec používat. Ve skutečnosti se však může jednat o velmi užitečnou technologii, jež umožňuje tvořit aplikace poněkud jiným způsobem, než je tomu obvykle zvykem. Většina dnešních programovacích jazyků je totiž založena na imperativním způsobu zápisu programů, v nichž se explicitně zapisuje tok běhu programu: sekvence příkazů, rozvětvení programu, programové smyčky, rekurze. Alternativně je ovšem možné využít i způsob, který můžeme nalézt v systémech s architekturou dataflow – v těchto systémech se (dosti zjednodušeně řečeno) popisuje, co se má stát ve chvíli, kdy do nějakého funkčního bloku přijdou všechny potřebné vstupní údaje a už se většinou explicitně neuvádí, v jakém pořadí jsou jednotlivé funkční bloky vykonávány. Čistě teoreticky mohou všechny funkční bloky pracovat paralelně, což je také jeden z důvodů, proč se v minulosti dataflow systémům věnovala poměrně velká pozornost.

V současnosti lze za dataflow systém považovat například grafický akcelerátor, ovšem právě s použitím objektů typu promise a taktéž s využitím objektů future je možné podobný systém vytvořit i v jazyku Clojure. Ostatně podívejme se na jednoduchý demonstrační příklad:

; funkce pro velmi pomalý výpočet členů Fibonacciho řady
(defn fibonacci
    [n]
    (if (> n 2)
        n
        (+ (fibonacci (- n 2)) (fibonacci (- n 1)))))
 
 
; vytvoření tří objektů typu promise
(def x (promise))
(def y (promise))
(def z (promise))
 
; tři asynchronně běžící výpočty,
; v nichž se postupně (někdy v budoucnu)
; nastaví hodnoty všech tří objektů
; typu promise
(def task-1
    (future
        (deliver z (+ @x @y))))
 
(def task-2
    (future
        (deliver x (fibonacci 35))))
 
(def task-3
    (future
        (deliver y (fibonacci 40))))
 
 
; výpočty nyní běží, přičemž ve skutečnosti první
; výpočet musí čekat na dokončení ostatních dvou
; asynchronních vláken
 
; kdykoli se můžeme dotázat na @x, @y či @z

6. Technologie STM – Software Transactional Memory

S využitím objektů typu future je možné, jak již po přečtení předchozích odstavců víme, velmi snadno realizovat asynchronně běžící výpočty. Podobně je tomu i v případě takzvaných agentů, s nimiž se podrobněji seznámíme v následující části tohoto seriálu. Při programování reálných vícevláknových aplikací se však často setkáme s problémy, které lze vyřešit buď složitě s využitím zámků či synchronizovaných bloků (což je postup používaný velmi často v Javě, která pro tyto účely má vyhrazeno i klíčové slovo), nebo jednodušší cestou – pomocí transakcí. Jednou z důležitých technologií, kterou programovací jazyk Clojure vývojářům nabízí, je totiž i takzvaná STM neboli „Software Transactional Memory“, díky níž lze transakce realizovat. Pod pojmem transakce rozumíme takovou operaci, která se z hlediska ostatních vláken provede atomicky, tj. v jeden okamžik. Současně je transakce prováděna izolovaně; operace prováděné uvnitř transakce (změna některých referencí atd.) není viditelná zvenku až do chvíle, kdy je transakce ukončena. Taktéž je možné spustit transakci uvnitř jiné transakce – vnitřní transakce v tomto případě provede commit–uložení výsledků až současně s transakcí vnější (což je očekávatelné chování). Obecně je možné říci, že transakce v Clojure splňují vlastnost ACID (viz též databázové transakce), s tím, že o konzistenci se musí postarat sám programátor.

Transakce implementované v programovacím jazyce Clojure navíc nepoužívají žádné zámky a snaží se být při svém spouštění „optimistické“. Co to v praxi vlastně znamená? Pokud je nějaká část kódu vykonávána v transakci, provede se nejprve kopie všech referencí, které jsou uvnitř transakce měněny a všechny operace se následně provádí nad těmito kopiemi (zvnějšku to tedy vypadá tak, že transakce nemění žádné jiné objekty). Navíc se všechny transakce mohou spouštět paralelně a nezávisle na sobě. Co se však stane v případě, že nějaká transakce po svém ukončení zjistí, že jí vypočtené hodnoty, které se snaží commitovat, již byly mezitím změněny? Systém v tomto případě jednoduše transakci spustí znova a využije přitom již změněné hodnoty. V praxi to sice může znamenat, že nastávají situace, kdy bude jedna transakce spuštěna klidně i několikrát, ovšem zde velmi záleží na tom, co konkrétní aplikace vlastně provádí – uvádí se, že obecně je využití transakcí rychlejší, než explicitní použití zámků/synchronizovaných bloků, ovšem může se stát, že aplikace bude mít spuštěno několik tisíc transakcí, které na sobě budou závislé, a zde bude již situace odlišná (popravdě řečeno bych však nechtěl takovou aplikaci využívající zámky/synchronizované bloky ladit).

7. Funkce a makra používané při práci s transakcemi

V transakcích se většinou pracuje nikoli s globálními proměnnými, ale s objekty typu ref. S těmito typy objektů jsme se sice prozatím ještě nesetkali, i když je na nich v podstatě založena filozofie jazyka Clojure, ovšem práce s nimi je z uživatelského hlediska velmi jednoduchá. Objekt typu ref se vytvoří zavoláním funkce ref, které se předá i počáteční hodnota tohoto objektu. Aby se s ref mohlo dále pracovat, navazuje se obvykle na nějaký symbol s využitím speciální formy def (tím se objekt „pojmenuje“). Podívejme se na dvě definice objektu typu ref s navázáním těchto objektů na dvojici globálních symbolů:

user=> (def my-ref (ref 42))
#'user/my-ref
 
user=> (def string-ref (ref "Hello world"))
#'user/string-ref

Hodnotu, resp. přesněji řečeno stav objektu ref lze získat s využitím funkce deref:

user=> (deref my-ref)
42
 
user=> (deref string-ref)
"Hello world"

Pro zjednodušení se však v reálných programech namísto volání deref používá spíše makro preprocesoru zapisované pomocí zavináče:

user=> @my-ref
42
user=> @string-ref
"Hello world"

Nyní se již konečně dostáváme k použití objektů typu ref v transakcích. Pro změnu stavu těchto objektů slouží funkce ref-set a taktéž funkce alter. Odlišnost mezi těmito funkcemi spočívá v tom, že se pomocí ref-set přímo nastavuje nová hodnota (nový stav ref), zatímco přes alter je hodnotu možné změnit tak, že se na původní stav zavolá vybraná funkce, která samozřejmě může mít i další parametry. Nejprve se podívejme na to, co se stane ve chvíli, kdy se pokusíme zavolat funkci ref-set mimo transakci:

user=> (ref-set my-ref 10)
IllegalStateException No transaction running  clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)
 
user=> (ref-set string-ref "xyzzy")
IllegalStateException No transaction running  clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)

Je vidět, že Clojure v tomto případě nebyl příliš nadšený :-), jelikož je funkce ref-set určena pro použití uvnitř transakcí a nikoli pro obecné použití mimo transakce. Podobně to dopadne v případě funkce alter:

user=> (alter my-ref + 10)
IllegalStateException No transaction running  clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)

Aby se změna stavu objektu ref podařila, musíme ji provést v transakci, která se nejjednodušeji zapisuje pomocí makra dosync. V tomto makru může být uvedeno libovolné množství forem, které se postupně provedou a to takovým způsobem, že zaručí všechny vlastnosti transakcí – tj. klasický ACID:

user=> (dosync (ref-set my-ref 10))
10

Více příkazů v jedné transakci se zapisuje následujícím způsobem:

(dosync
    (ref-set my-ref 20)
    (ref-set string-ref "xyzzy"))

Zbývá nám ukázat funkci alter, která se taktéž musí volat uvnitř transakce:

user=> (dosync (alter my-ref + 10))
30

Popř:

(dosync
    (ref-set string-ref "root.cz")
    (alter my-ref * 2))

Čtení hodnoty (stavu) objektu ref lze provést kdykoli, tj. i mimo transakci:

user=> @string-ref
 
"root.cz"
user=> @my-ref
40

Při práci s transakcemi je nutné dbát především na to, aby se do funkce alter předávaly jen takové funkce, které nemají vedlejší efekt. Proč tomu tak musí být? Vzpomeňme si na předchozí kapitolu, kde jsme si řekli, že transakce může být v případě potřeby spuštěna i několikrát za sebou, takže si asi dokážete představit, jaké problémy by funkce s vedlejším efektem mohla v reálných aplikacích způsobit.

8. Demonstrační příklad: transakce při převodu peněz mezi dvěma účty

Typickým příkladem, na němž se velmi často vysvětlují databázové transakce, je funkce (v SQL databázích spíše uložená procedura) provádějící převod peněz z jednoho účtu na druhý. V reálném světě jde z pochopitelných důvodů o dosti choulostivou operaci, u níž je (alespoň z pohledu většiny účastníků :-) žádoucí, aby byly peníze buď skutečně převedeny a obsahy obou účtů náležitě změněny, nebo aby naopak obsahy obou účtů zůstaly nezměněny v případě, kdy by nějaká část transakce z různých důvodů selhala. Podobnou aplikaci s transakcemi můžeme naprogramovat i s využitím programovacího jazyka Clojure a bude se kupodivu jednat o velmi jednoduchý program. Nejprve pro jednoduchost vytvoříme tři globální symboly nazvané account-1, account-2 a account-3, které budou představovat trojici účtů v bance. Na prvním účtu je na začátku aplikace uloženo 10000 Kč, na druhém účtu 20000 Kč a na účtu třetím nejsou prozatím uloženy peníze žádné:

(def account-1 (ref 10000))
(def account-2 (ref 20000))
(def account-3 (ref 0))

Pro další zkrácení programu si nadefinujeme pomocnou funkci nazvanou print-accounts, která slouží k tisku aktuálního obsahu všech tří účtů (obecně však není dobré programovat podobné funkce, které přistupují přímo ke globálním symbolům; berme to jako daň za snahu o co nejkratší program):

(defn print-accounts []
    (println "Account 1 : " @account-1)
    (println "Account 2 : " @account-2)
    (println "Account 3 : " @account-3))

Nyní již můžeme naprogramovat nejdůležitější a nejzajímavější část celého demonstračního příkladu. Jedná se o funkci nazvanou příhodně transfer-money, která odečte od prvního vybraného účtu (předanému jako první parametr) zadanou sumu a přesně tutéž sumu přičte ke druhému účtu (předanému jako druhý parametr). Tento přesun peněz je proveden v transakci, tj. uvnitř makra dosync. Modifikace obsahu obou účtů zajišťuje funkce alter, která na hodnotu prvního účtu aplikuje funkci – a na hodnotu druhého účtu pak funkci +:

(defn transfer-money [from to amount]
    (dosync
        (alter from - amount)
        (alter to + amount)))

Nyní si již můžeme si celý program otestovat:

widgety

(println "Before transactions: ")
(print-accounts)
 
Before transactions:
Account 1 :  10000
Account 2 :  20000
Account 3 :  0
nil

Počáteční hodnota účtů tedy byla nastavena dobře. Zkusíme provést dvojici transakcí:

(transfer-money account-1 account-2 1000)
(transfer-money account-2 account-3 10000)

A vypsat hodnoty účtů po provedení obou transakcí:

(println "After transactions: ")
(print-accounts)
 
After transactions:
Account 1 :  9000
Account 2 :  11000
Account 3 :  10000
nil

9. Odkazy na Internetu

  1. Clojure home page
    http://clojure.org/downloads
  2. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  3. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  4. 4Clojure
    http://www.4clojure.com/
  5. ClojureDocs
    http://clojuredocs.org/
  6. Clojure (Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  7. Clojure (Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  8. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  9. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  10. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  11. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  12. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun.com/develo­per/technicalArticles/Dyn­TypeLang/
  13. JSR 223: Scripting for the JavaTM Platform
    http://jcp.org/en/jsr/detail?id=223
  14. JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
    http://jcp.org/en/jsr/detail?id=292
  15. Java 7: A complete invokedynamic example
    http://niklasschlimm.blog­spot.com/2012/02/java-7-complete-invokedynamic-example.html
  16. InvokeDynamic: Actually Useful?
    http://blog.headius.com/2007/01/in­vokedynamic-actually-useful.html
  17. A First Taste of InvokeDynamic
    http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html
  18. Java 6 try/finally compilation without jsr/ret
    http://cliffhacks.blogspot­.com/2008/02/java-6-tryfinally-compilation-without.html
  19. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  20. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  21. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  22. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  23. Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
    http://www.root.cz/clanky/vyuziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/
  24. Root.cz: JamVM aneb alternativa k HotSpotu nejenom pro embedded zařízení a chytré telefony
    http://www.root.cz/clanky/jamvm-aneb-alternativa-k-hotspotu-nejenom-pro-embedded-zarizeni-tablety-a-chytre-telefony/
  25. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  26. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  27. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  28. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  29. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  30. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  31. BCEL Home page
    http://commons.apache.org/bcel/
  32. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  33. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  34. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  35. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  36. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  37. ASM Home page
    http://asm.ow2.org/
  38. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  39. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  40. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  41. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  42. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  43. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  44. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  45. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  46. Cobertura
    http://cobertura.sourceforge.net/
  47. FindBugs
    http://findbugs.sourceforge.net/
  48. GNU Classpath
    www.gnu.org/s/classpath/
  49. Java VMs Compared
    http://bugblogger.com/java-vms-compared-160/
  50. JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
    http://www.jcp.org/en/jsr/de­tail?id=223
  51. Scripting for the Java Platform
    http://java.sun.com/develo­per/technicalArticles/J2SE/Des­ktop/scripting/
  52. Scripting for the Java Platform (Wikipedia)
    http://en.wikipedia.org/wi­ki/Scripting_for_the_Java_Plat­form
  53. Java Community Process
    http://en.wikipedia.org/wi­ki/Java_Specification_Requ­est
  54. Java HotSpot VM Options
    http://www.oracle.com/technet­work/java/javase/tech/vmop­tions-jsp-140102.html
  55. Great Computer Language Shootout
    http://c2.com/cgi/wiki?Gre­atComputerLanguageShootout
  56. Java performance
    http://en.wikipedia.org/wi­ki/Java_performance
  57. Trying the prototype
    http://mail.openjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  58. Better closures (for Java)
    http://blogs.sun.com/jrose/en­try/better_closures
  59. Lambdas in Java: An In-Depth Analysis
    http://www.infoq.com/articles/lambdas-java-analysis
  60. Class ReflectiveOperationException
    http://download.java.net/jdk7/doc­s/api/java/lang/Reflective­OperationException.html
  61. Scala Programming Language
    http://www.scala-lang.org/
  62. Run Scala in Apache Tomcat in 10 minutes
    http://www.softwaresecret­weapons.com/jspwiki/run-scala-in-apache-tomcat-in-10-minutes
  63. Fast Web Development With Scala
    http://chasethedevil.blog­spot.cz/2007/09/fast-web-development-with-scala.html
  64. Top five scripting languages on the JVM
    http://www.infoworld.com/d/developer-world/top-five-scripting-languages-the-jvm-855
  65. Proposal: Indexing access syntax for Lists and Maps
    http://mail.openjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  66. Proposal: Elvis and Other Null-Safe Operators
    http://mail.openjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  67. Java 7 : Oracle pushes a first version of closures
    http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/
  68. Groovy: An agile dynamic language for the Java Platform
    http://groovy.codehaus.org/Operators
  69. Better Strategies for Null Handling in Java
    http://www.slideshare.net/Step­han.Schmidt/better-strategies-for-null-handling-in-java
  70. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  71. Java Virtual Machine
    http://en.wikipedia.org/wi­ki/Java_virtual_machine
  72. ==, .equals(), compareTo(), and compare()
    http://leepoint.net/notes-java/data/expressions/22com­pareobjects.html
  73. New JDK7 features
    http://openjdk.java.net/pro­jects/jdk7/features/
  74. Project Coin: Bringing it to a Close(able)
    http://blogs.sun.com/darcy/en­try/project_coin_bring_clo­se
  75. CloseableFinder source code
    http://blogs.sun.com/darcy/re­source/ProjectCoin/Closea­bleFinder.java
  76. Joe Darcy blog about JDK
    http://blogs.sun.com/darcy
  77. Java 7 – more dynamics
    http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/
  78. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun.com/develo­per/technicalArticles/Dyn­TypeLang/index.html
Našli jste v článku chybu?
Vitalia.cz: 5 důvodů, proč jet na výlov rybníka

5 důvodů, proč jet na výlov rybníka

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

DigiZone.cz: Další programatické formáty

Další programatické formáty

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

Vitalia.cz: Antibakteriální mýdla nepomáhají, spíš škodí

Antibakteriální mýdla nepomáhají, spíš škodí

Podnikatel.cz: Letáky? Lidi zuří, ale ony stále fungují

Letáky? Lidi zuří, ale ony stále fungují

Root.cz: Hořící telefon Samsung Note 7 zapálil auto

Hořící telefon Samsung Note 7 zapálil auto

Vitalia.cz: Nová vakcína proti chřipce se aplikuje nosem

Nová vakcína proti chřipce se aplikuje nosem

DigiZone.cz: Regionální tele­vize CZ vysílá "Mapu úspěchu"

Regionální tele­vize CZ vysílá "Mapu úspěchu"

Lupa.cz: Odkazy na pirátský obsah mohou být nelegální

Odkazy na pirátský obsah mohou být nelegální

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr

DigiZone.cz: Sat novinky: NASA Ultra HD (4K)

Sat novinky: NASA Ultra HD (4K)

DigiZone.cz: Technisat připravuje trojici DAB

Technisat připravuje trojici DAB

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

Lupa.cz: Blíží se konec Wi-Fi sítí bez hesla?

Blíží se konec Wi-Fi sítí bez hesla?

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Vitalia.cz: Muž, který miluje příliš. Ženám neimponuje

Muž, který miluje příliš. Ženám neimponuje

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel