niedziela, 18 maja 2008

Java Killers #006

Po dłuższej przerwie, spowodowanej moją niekończącą się walką z pracą magisterską, mam przyjemność przedstawić wam koleją odsłonę cyklu Java Killers. Jak zwykle, aby nie przedłużać, spójrzmy na poniższy kod:


5 package javakillers.part006;
6
7 /**
8 *
9 * @author pawel
10 */
11 public class Main
12 {
13 public static void main(String[] args)
14 {
15 Main m = new Main();
16 m.run();
17 }
18
19 public void run()
20 {
21 int a = 0;
22 try
23 {
24 while(a < Integer.MAX_VALUE + 1)
25 {
26 a++;
27 }
28
29 if(a >= 0)
30 {
31 System.exit(0);
32 }
33 }
34 finally
35 {
36 System.out.println("a wynosi: " + a);
37 }
38 }
39 }
40
41


I standardowe pytanie: co zwróci powyższy kod. Dziś do wyboru aż pięć możliwych odpowiedzi, z czego jak zwykle tylko jedna jest prawdziwa:

A) a wynosi: 2147483647
B) a wynosi: -2147483648
C) a wynosi: 0
D) program się zapętli i sam z siebie nie zakończy działania
E) program się zakończy, ale nic nie napisze

Zanim przeczytasz rozwiązanie, spróbuj pomyśleć nad odpowiedzią.





Prawidłowa odpowiedź to oczywiście E) program się zakończy, ale nic nie napisze. Rozwiązanie jest trywialne, jeśli znane są nam dwie cechy języka Java:

1. Integer.MAX_VALUE + 1 zwróci nam liczbę ujemną [nie wiesz czemu? sprawdź w googlach, warto wiedzieć!]
2. Wywołanie System.exit(0); powoduje natychmiastowe zabicie procesu, przez co blok finally { ... } nigdy się nie wykona, mimo, że w oficjalnym tutorialu do języka Java możemy przeczytać " The finally block always executes when the try block exits". Jak widać nie zawsze i warto mieć tego świadomość.

I to koniec na dziś! Na zakończenie pytanie jeszcze dla ambitnych: kiedy jeszcze nie wykona się blok finally { ... }? Innymi słowy co, w try {... } musi się pojawić, żeby zawartość finally { ... } nigdy się nie wykonała?

8 komentarzy:

morisil pisze...

Runtime.getRuntime().halt(0);

morisil pisze...

Jest jeszcze jedna możliwość - zblokować wątek wykonujący aktualnie kod wewnątrz try-finally, po czym w innym wątku zamknąć maszynę wirtualną. :)

Paweł Szulc pisze...

@morisil: dobrze :), mozna jeszcze zrobic pele while(true) w bloku try, wtedy finally tez sie nigdy nie wykona :)

morisil pisze...

To i ja mam zagadkę - napisałeś "Wywołanie System.exit(0); powoduje natychmiastowe zabicie procesu", co nie jest do końca prawdą - JVM może z powodzeniem wykonywać kod (a przynajmniej jego część :) ) naszej aplikacji po wywołaniu System.exit - ale jak to uzyskać?

Anonimowy pisze...

Wystarczy zarejestrować metodę wywoływaną przy zamykaniu wirtualnej maszyny
Runtime.getRuntime().addShutdownHook()

Adam Wozniak pisze...

Cześć Paweł
Fajny cykl wpisów (Java Killers).
Z takich zagadek ostatnio przyglądałem się mechanizmowi Double-checked locking ( http://en.wikipedia.org/wiki/Double-checked_locking ). Zastanawiałem się nad użyciem tego mechanizmu. Ten artykuł na Wikipedii (i linki do stron z niego wychodzące) są ciekawą lekturą dla wszystkich zajmujących się współbieżnymi aspektami w Java.

Pozdrawiam, Adam Woźniak

Tomasz pisze...

Cykl faktycznie ciekawy.
Drobna uwaga:
jak bym zobaczył w kodzie porządnego projektu konstrukcję w stylu System.exit(0) albo wspomniane sztuczki z Runtime.getRuntime() to bym chyba autora wysłał na Madagaskar do wyrębu baobabów.

Stosowanie tego typu konstrukcji znakomicie utrudnia moim zdaniem śledzenie przebiegu programu i znalezienie miejsca wystąpienia błędów.

morisil pisze...

@tomasz

Byłbym ostrożny w formułowaniu tak kategorycznych stwierdzeń. Proponuję przyjrzeć się kodowi źródłowemu:

com.sun.tools.javac.Main

Rozumiem, że to nie jest "porządny" projekt. ;)

Narzędzia CLI napisane w Javie czasem wymagają przekazania "exit code" do środowiska z którego zostały wywołane. Nie chcę Cię martwić, ale każdy porządny projekt uruchamiany z CLI jak np servlet container będzie miał taką sekwencję.

Nie zmienia to faktu że należy System.exit używać z głową. Rozumiem Twoją intencję odnośnie niebezpieczeństw System.exit - ale czy w "porządnych" projektach nie używa się przypadkiem SecurityManages? Wtedy próba wywołania System.exit w kodzie zarządzanym przez kontener wywala po prostu SecurityException.

Co do Runtine.getRuntime() - rejestracja własnych shutdown hooks np. jest bardzo pożądana w przypadku pisania własnych kontenerów/narzędzi CLI. Obserwowanie własnego kodu pięknie zamykającego wszystkie otwarte zasoby po wywołaniu: killall java z konsoli sprawia nie lada satysfakcję - mogę zaręczyć. :)