Posts tagged ‘Java’

Przerywanie działania wątku

2008-05-12 21:17

Jednym z powodów używania wątków jest możliwość przerwania wykonywanych przezeń czynności właściwie w dowolnym momencie. Dzięki temu można na przykład wyposażyć aplikację okienkową w magiczny przycisk Anuluj obok paska postępu. Przez to zaś użytkownik ma wrażenie, że – nawet jeśli musi (dłuższą) chwilę zaczekać – nadal jest panem sytuacji :)
Jak można więc przerwać wątek, jeśli zachodzi taka potrzeba? Sposobów jest kilka:

  1. Najelegantszym jest sprawienie, aby zakończył się on “sam z siebie” i poczekanie na niego. Polega to na ustawieniu flagi sygnalizującej wątkowi konieczność zakończenia, którą oczywiście musi on regularnie sprawdzać. Stąd też główne pętle występujące w procedurach wątków są często w stylu while (!Terminating) { /* ... */ }. Dla porządku warto też (a w niektórych środowiskach trzeba), po ustawieniu rzeczonej flagi na wątek zaczekać. Czyni się to zwykle funkcją z join (‘złącz’) w nazwie: Thread.Join/join w .NET/Javie, pthread_join w POSIX-ie i… WaitForSingleObject w Windows API :)
  2. Jeśli wątek sporą część czasu poświęca na czekanie lub chociaż co jakiś czas przechodzi przynajmniej na krótko w stan uśpienia, wówczas można mu przerwać (interrupt). W .NET/Javie robi się to poprzez, niespodzianka, Thread.Interrupt/interrupt. Wówczas przy następnym wejściu do funkcji czekającej w rodzaju Thread.Sleep/sleep rzucany jest wyjątek (Thread)InterruptedException, który odwija stos wątku, kończąc w ten sposób jego działanie. W natywnych platformach nie może być oczywiście dokładnego odpowiednika podobnej operacji, lecz można ją symulować czekaniem na jakimś obiekcie synchronizacyjnym i sygnalizowaniem go, gdy chcemy to czekanie przerwać. W POSIX-ie funkcja pthread_cancel działa aczkolwiek w dość podobny sposób do Interrupt, pozwalając na posprzątanie zasobów w trakcie anulowania wątku.
  3. Najmniej eleganckim sposobem jest wreszcie brutalne przerwanie wątku, czyli próba jego natychmiastowego zakończenia w dowolnym momencie. Zarówno .NET, jak i Java rozwiązały to w podobny sposób: po wywoływaniu odpowiedniej metody (Thread.Abort/stop), w wątku rzucany jest bardzo specjalny wyjątek (ThreadAbortException lub ThreadDeath). Ma on tę cechę, że… przechodzi przez (prawie) wszystkie bloki catch – w .NET właściwie nie można go “zdusić”. To jednak sprawia, że taki wyjątek może wykonać po drodze wszystkie bloki finally, co pozwala zwolnić zawłaszczone zasoby i blokady oraz przywrócić obiekty synchronizujące do właściwego stanu. Występująca w Windows API funkcja TerminateThread takiej możliwości już nam nie daje, przez co jej użycie może prowadzić do różnych kłopotów.

Wnioski z tych trzech sposobów są mniej więcej takie, że pisząc kod wykonywany w osobnym wątku powinniśmy zawsze przewidzieć “czysty” sposób na jego zakończenie. Poleganie na przerywaniu lub brutalnym zakańczaniu wątków może się bowiem źle skończyć.

Tags: , ,
Author: Xion, posted under Programming » 2 comments

Dodatki do kodu

2008-01-16 10:34

Komentarze umieszczamy w kodzie, aby opisać jego działanie. Są one przeznaczone wyłącznie dla osób czytających go i jedynie programy typu javadoc czy doxygen – służące automatycznemu generowaniu dokumentacji – mogą się niektórymi komentarzami interesować. Na pewno jednak nie robi tego kompilator.
Wymyślono jednak, że niektóre elementy kodu potrzebują innych, specyficznych “komentarzy”, przeznaczonych dla kompilatora właśnie. Różnie się one nazywają w różnych językach, ale ich głównym celem jest przekazanie dodatkowych informacji odnośnie klasy, metody, typu czy innego elementu programu, bez potrzeby stosowania.

W .NET takie dodatki nazywa się atrybutami i umieszcza przed wybraną deklaracją, w nawiasach kwadratowych (lub kątowych w przypadku Visual Basica), oddzielone przecinkami. Atrybuty te mogą dotyczyć właściwie wszystkiego, począwszy od wskazania na klasę, która może być serializowana (i pola, które nie powinny być) po informacje dla Form Designera na temat danej właściwości niestandardowego komponentu:

  1. [Category("Foos"), Description("A foo associated with the component")]
  2. public Foo Foo
  3. {
  4.    get { return foo; }
  5.    set { foo = value; }
  6. }

Ogólnie dotyczą one jednak różnych funkcji samej platformy .NET i służą wskazaniu, które elementy pełnią określone role w różnych rozwiązaniach, które działają w jej ramach. Można aczkolwiek definiować także własne atrybuty, a potem w czasie działania programu wydobywać o nich informacje przy pomocy mechanizmu refleksji.

A jak to wygląda w Javie? Otóż tam od wersji 1.5 istnieją adnotacje. Ich nazwy poprzedza się znakiem @ i umieszcza w osobnych linijkach, poprzedzających deklaracje, których dotyczą. Ponieważ tutaj język jest ściśle związany z platformą (maszyną wirtualną Javy), adnotacje czasami pełnią funkcje “brakujących” słów kluczowych lub dyrektyw dla kompilatora. Typowy przykład to adnotacja Override:

  1. @Override
  2. public boolean equals(Object obj)  { /* ... */ }

którą możemy oznaczać przesłonięte wersje metod wirtualnych. Jest to praktyczne, gdyż w przypadku popełnienia błędu (np. literówki w nazwie) kompilator ostrzeże nas, że tak naprawdę zdefiniowaliśmy zupełnie nową metodę (bez tego błąd objawiłby się dopiero nieprawidłowym działaniem programu).
Naturalnie, możliwe jest też tworzenie własnych adnotacji oraz pobieranie informacji o nich przy pomocy refleksji. Aż korci, żeby sprawdzić, kto od kogo ściągał tutaj pomysły ;-)

W C++ deklaracje standardowo nie mają żadnych “ozdobników”, ale pod tym względem w różnych kompilatorach bywa różnie. Na przykład w Visual C++ mamy słówko __declspec, które służy do całego mnóstwo różnych celów. Wśród nich są chociażby takie oto warianty:

  • __declspec(align(n)) służy do określania, jak dane (np. pola struktur) mają być wyrównane w pamięci. Dzięki temu będą one umieszczone tak, by zajmowały zawsze wielokrotność podanych n bajtów, co przy odpowiedniej wartości (np. 32) może zwiększyć wydajność lub (dla 1) zmniejszyć zajętość pamięci.
  • __declspec(deprecated) pozwala oznaczyć dany element kodu jako przestarzały. Jego użycie będzie skutkowało wtedy ostrzeżeniem kompilatora.
  • __declspec(dllexport) i __declspec(dllimport) służą do tworzenia symboli eksportowanych w bibliotece DLL i do importowania tych symboli w innym programie.
  • __declspec(property) wprowadza konstrukcję właściwości do klasy, bardzo podobną do tych obecnych w Delphi. Po podaniu jednej lub dwóch metod dostępowych (do odczytu i ew. zapisu), otrzymujemy właściwość o danej nazwie i typie. Jaka szkoda, że to nieprzenośne :)

Zasadniczo Visual C++ posiada też atrybuty podobne do .NETowych, które są konieczne do tworzenia interfejsów COM. Na szczęście nimi, jak i samym COM-em, nie trzeba już sobie zaprzątać głowy :)

Tags: , , , ,
Author: Xion, posted under Programming » 2 comments

Wyliczenia na sterydach

2007-12-28 21:57

Co można powiedzieć o typach wyliczeniowych (enums)? Choćby to, że przydają się tam, gdzie mamy do czynienia ze stanami lub atrybutami, które należą do jakiegoś skończonego zbiory. Z technicznego punktu widzenia to kilka(naście/dziesiąt/set) liczb nazwanych stałymi identyfikatorami. I zazwyczaj języki programowania nie oferują wiele ponad to.
Lecz Java musiała się tu wyróżnić :) Z wierzchu jej enumy wyglądają niby zwyczajnie:

  1. public enum Answer { YES, NO, DONT_KNOW, DONT_CARE }

ale w środku kryją bardzo szerokie i pewnie trochę udziwnione możliwości…

Zacznijmy od tego, że takie stałe wyliczeniowe są bezpieczne pod względem typowania – nie można więc jawnie rzutować ich na zwykłe liczby typu int. Słowo kluczowe enum tworzy też – podobnie jak w C# – przestrzeń nazw, więc do stałych odwołujemy się nazwami kwalifikowanymi (Answer.YES) i nie trzeba stosować żadnych przedrostków.
Ale to są wręcz oczywistości. Kolejnym punktem programu jest możliwość dostępu do zbioru zadeklarowanych stałych oraz ich wypisywania w postaci tekstowych nazw:

  1. System.out.println ("[Poll] Are enums in Java too exaggerated?");
  2. for (Answer answer : Answer.values())
  3.    System.out.printf ("( ) %s\n", answer);

Takie nazwy mogą być jednak brzydkie. Żaden problem, bowiem w Javie z każdą stałą może być związany dowolny zestaw danych:

  1. public enum Answer
  2. {
  3.    YES("Yes"), NO("No"), DONT_KNOW("I don't know"), DONT_CARE("I don't care");
  4.  
  5.    // deklaracja dodatkowych danych w postaci pól
  6.    private final String message;
  7.  
  8.    // konstruktor (!)
  9.    Answer(String message)   { this.message = message; }
  10.  
  11.    // metoda dostępowa
  12.    public String getMessage()   { return message; }
  13. }

które – jak widać – deklarujemy podobnie jak składniki klas. A poszczególne “stałe” typu wyliczeniowego nie są już na pewno tylko liczbami: to pelnoprawne obiekty:

  1. System.out.println ("[Poll] Is this poll much better?");
  2. for (Answer answer : Answer.values())
  3.    System.out.printf ("( ) %s\n", answer.getMessage());

Widzimy, że metody są również dozwolone. Żeby było śmieszniej, mogą być one nawet “wirtualne”. Możemy bowiem zapewnić, aby inne ich wersje były wywoływane dla różnych stałych. Robi wrażenie, prawda?…

Cóż, pewnie niekoniecznie – zwłaszcza, że nietrudno osiągnąć podobną funkcjonalność przez instrukcję wyboru. Zgoda, będzie ona bardziej podatna i trudniejsza w konserwacji. Lecz jeśli alternatywą jest przeładowanie języka dziwnymi i nie do końca oczywistymi mechanizmami, to stary dobry switch niekoniecznie musi być rozwiązaniem złym.
Co oczywiście nie zabrania nam być pod wrażeniem pomysłowości twórców języka Java :-)

Tags: ,
Author: Xion, posted under Programming » 12 comments

Synchronizacja wątków w Javie

2007-12-07 23:55

Deweloperzy programujący wielowątkowo zapewne znają klasyczne typy wykorzystywanych przy okazji obiektów. Są to na przykład semafory, sekcje krytyczne (zwane też semaforami binarnymi) czy zdarzenia (events). Wszystkie one służą oczywiście do synchronizacji wątków tak, aby wykluczyć jednoczesny, wykluczający się dostęp do jednego zasobu.

Tego typu obiekty są wykorzystywane jednak głównie wtedy, kiedy mechanizm wątków jest zrealizowany w sposób specyficzny dla systemu operacyjnego – jak choćby poprzez API z Windows lub bibliotekę pthreads z Linuxa. Jeśli jednak mamy szczęście pracować z językiem, którego wielowątkowość jest częścią, wówczas korzysta się zwykle z nieco innych technik.
Taka sytuacja jest na przykład w Javie. Tam każdy obiekt (czyli instancja klasy dziedziczącej z java.lang.Object) może być użyty jako obiekt synchronizujący. Z grubsza działa to w ten sposób, że gdy jeden z wątków zadeklaruje wykorzystanie danego obiektu – przy pomocy słowa kluczowego synchronized – pozostałe nie mogą zrobić tego samego. Taka synchronizacja może odbywać się na wiele (składniowych) sposobów, jak choćby zadeklarowanie całej metody jak synchronizowanej:

  1. class SynchronizedCounter
  2. {
  3.    private int value = 0;
  4.    public synchronized int increment() { return ++value; }
  5. }

W tym prościutkim przykładzie mamy zagwarantowane, że żaden postronny wątek nie wtrąci się w operację inkrementacji ze zwróceniem wartości (która nie jest atomowa) i stan licznika będzie zawsze poprawny.
Tak więc mamy semafory tudzież sekcje krytyczne. A co np. ze zdarzeniami (sygnałami)? Otóż każdy obiekt posiada metody wait i notify, umożliwiające czekanie na powiadomienie z innego wątku i oczywiście wysłanie takiego powiadomienia. Całkiem skuteczne i dosyć proste; naturalnie na tyle, na ile proste może być programowanie wielowątkowe :)

Ale czy oryginalne? Otóż dziwnym trafem na platformie .NET cała sprawa wygląda niemal dokładnie tak samo :) Odwzorowania przytoczonych elementów Javy w C# to odpowiednio: lock (z dokładnością do kilku niuansów), Monitor.Wait i Monitor.Pulse. Sam sposób tworzenia wątków jest zresztą też bardzo bardzo podobny.
Wszelka zbieżność przypadkowa? Zdecydowanie nie. Lecz dobre rozwiązania warto jest przecież rozpowszechniać :]

Tags: , ,
Author: Xion, posted under Programming » 2 comments

Bolączki C++ #6 – Obsługa zdarzeń

2007-11-09 23:46

Dzisiaj w programowaniu aplikacji obowiązują dwie proste i fundamentalne zasady. Po pierwsze, programujemy obiektowo i kod zamykamy w klasy z polami i metodami. Po drugie, tworzymy programy działające w środowisku graficznym, z okienkami, przyciskami, polami tekstowymi i innymi klasycznymi elementami interfejsu.
Jednak jeden plus dwa równa się kłopoty, przynajmniej w C++. Już raz narzekałem zresztą na to, że w tym języku obiektowość i GUI to nie jest najlepsze połączenie. Zgrzyta tu mechanizm obsługi zdarzeń generowanych przez interfejs graficzny.

Wcześniej napisałem, że możliwym rozwiązaniem problemu jest zasymulowanie w jakiś sposób delegatów, czyli – z grubsza – wskaźników na metody obiektów. To jedna z dróg radzenia sobie z kwestią obsługi zdarzeń. Ale też inna, wykorzystująca mechanizm metod wirtualnych i polimorfizmu. Polega to na zdefiniowaniu jednolitej klasy bazowej dla obiektów, które mają odbierać zdarzenia. Zwie się je zwykle event handlers, co jak zwykle nie ma dobrego tłumaczenia. Taki handler wyglądać może na przykład tak:

  1. class IEventHandler
  2. {
  3.    public:
  4.       virtual void OnClick(IControl* pSender) = 0;
  5.       virtual void OnKeyDown(IControl* pSender) = 0;
  6.       // itd.
  7. };

Mając jakiś element interfejsu użytkownika, np. przycisk, przekazujemy mu nasz własny obiekt implementujący powyższy interfejs. Metody tego obiekty są następnie (polimorficznie) wywoływane w reakcji na odpowiednie zdarzenia.
Tak oczywiście można robić w C++, ale nie jest to zbyt wygodne. Tym czego znów brakuje, to niestatyczne klasy wewnętrzne, obecne choćby w Javie, których brak w C++ nie da się do końca zastąpić wielokrotnym dziedziczeniem.

Albo delegaty, albo sposób opisany przed chwilą – zapewne nie ma żadnej innej drogi obiektowej obsługi zdarzeń. Niestety, żaden z tych sposobów nie jest obecnie wspierany w C++ i nie zanosi się, by miało się to wkrótce zmienić.

Tags: , , ,
Author: Xion, posted under Programming » 2 comments

Grubo zapakowane I/O

2007-11-06 12:02

W każdym języku programowania potrzebny jest system wejścia/wyjścia. To zresztą bardzo często wykorzystywana jego część, więc powinna charakteryzować się wysoką jakością. Chcielibyśmy mianowicie nie tylko tego, aby I/O było szybkie. Powinno być też elastyczne i proste w obsłudze. Czasami udaje się te wymagania pogodzić w całkiem zadowalający rezultat, a czasem nie.

Weźmy na przykład Javę. Posiada ona bardzo rozwinięty system wejścia/wyjścia, umożliwiający odczyt i zapis z wielu różnych źródeł: ekranu, plików, gniazdek sieciowych, potoków łączacych wątki, itp. Ponadto komunikacja może odbywać się na wiele sposobów: mamy na przykład dość “surowe” strumienie, nieco bardziej użyteczne czytacze (readers) i zapisywacze (writers), a także kanały (channel) i bufory (buffers).
Cały system wydaje się zatem bardzo zaawansowany. Niestety, w praktyce jest on zdecydowanie przerośnięty, a poza tym charakteryzuje się pewną ‘ciekawą’ cechą – nazwijmy to – projektową. Osobiście uważam, że twórcy Javy w tym momencie przedobrzyli i chcąc zastosować bardzo elastyczny w założeniu wzorzec Dekorator, stworzyli interfejsowy koszmarek. Wspomniany wzorzec polega na kolejnym “opakowywaniu” obiektów tak, aby rozszerzać ich możliwości; obiekt ‘zewnętrzny’ nie musi przy tym wiedzieć dokładnie, czym jest obiekt ‘wewnętrzny’. I tutaj rzeczywiście tak jest, lecz na nieszczęscie sami musimy zawinąć obiekt w te wszystkie warstwy.
Przykład? System.in, czyli strumień standardowego wejścia, w swej pierwotnej postaci jest niemal zupełnie bezużyteczny. Żeby zrobić z nim cokolwiek sensownego (np. odczytać linię tekstu), musimy najpierw opakować go do postaci odpowiedniego czytacza:

Podobnie jest chociażby z plikami. Za każdym razem musimy ubrać nasz obiekt na cebulkę, aby był on przydatny, przechodząc przy okazji przez cały arsenał bardzo różnych klas, od których jest wręcz gęsto w JDK.
Trzeba aczkolwiek przyznać uczciwie, że System.IO z .NET też zawiera całe mnóstwo różnych klas. Tam konieczność podobnego opakowywania zachodzi jednak o wiele rzadziej, gdyż interfejsy tych klas są trochę inteligentniejsze.

A co ze “staroświeckimi” językami, jak C++ czy Delphi? No cóż, w nich operuje się głównie pojęciem uniwersalnych strumieni i w zasadzie niczego więcej. Nie trzeba ich jednak niczym otaczać, bo fabrycznie potrafią już chociażby operować na podstawowych typach danych, a nie tylko ciągach bitów. Niby to mniej elastyczne i nie tak “obiektowo czyste”, ale o ile przyjemniejsze w użyciu.

Tags: ,
Author: Xion, posted under Programming » 3 comments

Domykanie klas i metod

2007-09-09 12:10

W teorii dziedziczenie to niezbyt skomplikowana relacja między klasami, ale w praktyce potrzeba czasem niestandardowych rozwiązań. Niekiedy bowiem możliwość rozszerzenia hierarchii klas w nieskończoność nie jest wcale pożądana. Zazwyczaj chodzi tu o względy projektowe, rzadziej o efektywność.
Pytanie brzmi: czy można jakoś zabezpieczyć konkretną klasę przed dziedziczeniem (czyli zabronić w tworzenia opartych o nią klas pochodnych)?

W C# i Javie jest to możliwe za pomocą specjalnych słów kluczowych – odpowiednio sealed (‘zapieczętowany’) i final. Wystarczy opatrzyć nimi definicję klasy, by kompilator odmówił skompilowania kodu, który wykorzystywałby taką klasę jako bazową:

  1. public sealed class Foo { };
  2.  
  3. // błąd - dziedziczenie zapieczętowanej klasy
  4. public class Bar : Foo { };

Modyfikatory sealed/final można też stosować do metod wirtualnych. W tym przypadku sprawiają one, że dana metoda wirtualna nie może być nadpisywana w klasach pochodnych. Chwilowo nie przychodzi mi do głowy żaden pomysł na pokazanie przydatności takiego triku, ale zapewne takowy przykład istnieje :)

W C++ nie mamy rzecz jasna wspomnianych słów kluczowych (ani żadnych, które działałyby podobnie), ale istnieje sposób na zabezpieczenie klasy przed dziedziczeniem. Jest on jednak brzydki. A nawet bardzo brzydki. Mimo to zaprezentuję go.
Jak to często bywa w takich sytuacjach, sztuczka polega na kreatywnym wykorzystaniu pewnego mechanizmu językowego unikalnego dla C++, który generalnie służy do czegoś zupełnie innego. Tutaj chodzi o dziedziczenie wirtualne – a dokładniej o to, że wirtualna klasa bazowa musi być bezpośrednio inicjalizowana przez każdą klasę pochodną; nawet tą w setnym pokoleniu niżej. Do tego dodać należy jeszcze prywatne konstruktory i deklarację przyjaźni, a następnie zamieszać i doprawić do smaku, by ostatecznie upichcić coś w tym stylu:

  1. class CFoo; // deklaracja klasy, którą chcemy zabezpieczyć przed dziedziczeniem
  2.  
  3. // sztuczna klasa pomocnicza - "blokada"
  4. class CFoo_Lock
  5. {
  6.    // deklaracja przyjaźni
  7.    friend class CFoo;
  8.  
  9.    private:
  10.       // prywatne konstruktory
  11.       CFoo_Lock() { }
  12.       CFoo_Lock(const CFoo_Lock&) { }
  13. };
  14.  
  15. // zapieczętowana klasa
  16. class CFoo : public virtual CFoo_Lock { /* ... */ };
  17.  
  18. // próba dziedziczenia - błąd: brak dostępu do konstruktora CFoo_Lock
  19. class CBar : public CFoo { /* ... */ };

Klasa pomocnicza CFoo_Lock ma prywatny konstruktor domyślny, więc generalnie nie może być on wywoływany. W szczególności nie może być on wywołany przez konstruktor CBar – a jest to konieczne, gdyż CFoo_Lock jest wirtualną klasą bazową i musi być zainicjalizowana bezpośrednio przez dowolną klasę pochodną (nawet niebezpośrednio pochodną). Dlatego też próba dziedziczenia CFoo skończy się błędem mówiącym o braku dostępu do prywatnego konstruktora CFoo_Lock.
Dlaczego zatem CFoo działa? To już zasługa deklaracji przyjaźni umieszczonej w klasie-blokadzie. Przyjaźń międzyklasowa nie jest jednak dziedziczna, więc nasza blokada odmawia dostępu do prywatnych składowych (czyli konstruktorów) klasom pochodnym CFoo. Dzięki temu (i niuansom dziedziczenia wirtualnego) cały ten trik działa.

To rozwiązanie można naturalnie uczynić bardziej automatycznym – wystarczy zaprząc do pracy szablony i preprocesor, by uzyskać prawie to samo co sealed/final w C#/Javie. Trudno będzie jednak uznać to kombinowanie za eleganckie.
Wygląda więc na to, że najlepszym sposobem na zabezpieczenie klasy przed dziedziczeniem w C++, to po prostu… pokazać na nią palcem (tj. komentarzem) i powiedzieć “Nie dziedzicz mnie!”. No i mieć nadzieję, że ktoś nas posłucha.

Tags: , , ,
Author: Xion, posted under Programming » 1 comment
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.