public void run()
Transkrypt
public void run()
Programowanie sieciowe mgr Marcin Raniszewski mgr inŜ. Paweł Kośla Łódź, 2009 Wykład 6: Wielowątkowość, operacje na plikach, kolekcje 1 Plan wykładu Wątki (klasa Thread i interfejs Runnable) Synchronizacja „śycie” wątków Odczyt i zapis do plików Typowe kolekcje 2 Wielowątkowość Proces to egzemplarz wykonywanego programu (inaczej: program w trakcie wykonywania). KaŜdemu procesowi system operacyjny przydziela zasoby, takie jak: procesor, pamięć (przestrzeń adresowa), dostęp do urządzeń wejścia-wyjścia oraz plików. Wątek (Thread) to jednostka wykonawcza w obrębie jednego procesu, będąca kolejnym ciągiem instrukcji wykonywanym w obrębie tych samych danych (procesu). Wątki mogą korzystać z przestrzeni adresowej procesu, ale posiadają własne kopie zmiennych lokalnych. 3 Wielowątkowość W Javie istnieją dwa sposoby korzystania z wątków: - tworzenie klas pochodnych klasy Thread - implementacja interfejsu Runnable Klasa, której obiekty mają działać jako wątki musi zawierać metodę: public void run() class MyThread extends Thread { public void run() { ... } } ... MyThread watek = new MyThread(); lub: class MyThread implements Runnable { public void run() { ... } } ... Thread watek = new Thread(new MyThread()); 4 Wielowątkowość Wątki uruchamia się metodą watek.start(). Wówczas, wykonywane są instrukcje zaimplementowane w metodzie run() (niezaleŜnie od instrukcji głównego programu). Wątek trwa do zakończenia metody run(). Mimo, Ŝe istnieje takŜe metoda stop(), to nie naleŜy jej uŜywać do wymuszania zakończenia wątku – lepiej sprawdzać warunek logiczny wewnątrz run(). 5 Wielowątkowość - synchronizacja synchronized – zabezpiecza metody lub obiekty przed jednoczesnym dostępem przez wiele wątków. Aby wskazać, Ŝe pewne linie kodu powinny być wykonywane razem, naleŜy je umieścić w bloku synchronized, który przeprowadza synchronizację na pewnym obiekcie, którego te linie kodu dotyczą. Jeśli metoda oznaczona jako synchronized jest aktualnie wykonywana, to wszystkie kolejne jednoczesne próby jej realizacji zostaną wstrzymane aŜ do czasu zakończenia pierwszego wywołania. public synchronized void add(Object obj) { ... } synchronized (System.out) { //Pewne operacje na obiekcie System.out //Podczas wykonywania tego bloku przez pewien wątek załoŜona zostaje //blokada na dostęp do obiektu System.out //Jeśli pewien inny wątek chce skorzystać z obiektu System.out //zostaje zablokowany do czasu zdjęcia blokady z tego obiektu } 6 Wielowątkowość - synchronizacja Synchronizacja bloku jest tylko częściową blokadą obiektu, na którym jest załoŜona. Inne metody mogą korzystać z synchronizowanego obiektu o ile nie usiłują synchronizować się na tym obiekcie. Nie zawsze synchronizacja jest najlepszym rozwiązaniem i często warto pokusić się o rozwiązania alternatywne: uŜywanie zmiennych lokalnych (kaŜdy wątek działa na własnym zestawie zmiennych lokalnych), uŜywanie metod działających na argumentach typów prostych (przekazywanych przez wartość) lub bezargumentowych, tworzenie klas niezmiennych (deklaracja wszystkich pól jako prywatnych, nie pisanie metod modyfikujących pola klasy). 7 Wielowątkowość - priorytety KaŜdy wątek ma priorytet – w Javie jest to liczba całkowita z zakresu od 1 do 10. Wątek o priorytecie 10 będzie wykonywany w pierwszej kolejności, wątek o priorytecie 1 – w ostatniej. Gdy gotowych do wykonania jest wiele wątków, maszyna wirtualna Javy uruchamia tylko ten o najwyŜszym priorytecie. Domyślnym priorytetem dla tworzonych wątków jest 5. Priorytety wątków przydatne gdy chcemy niektórym z nich przydzielić więcej czasu procesora (wątki do komunikacji z uŜytkownikami – krótki czas odpowiedzi), a niektórym mniej (wątki dokonujące obliczeń, wątki długotrwałe – brak wymagania co do prędkości realizacji). Pola i metody klasy Thread związane z priorytetami: public static final int MIN_PRIORITY = 1 public static final int NORM_PRIORITY = 5 public static final int MAX_PRIORITY = 10 public final void setPriority(int newPriority) public final int getPriority() 8 Wielowątkowość - wywłaszczenie Wszystkie maszyny wirtualne Javy do zarządzania wątkami o róŜnych priorytetach uŜywają programu z wywłaszczeniowym harmonogramem wątków (wątek o wyŜszym priorytecie wywłaszcza <przerywa działanie> wątek o niŜszym priorytecie). W przypadku działania wielu wątków o równych priorytetach: program wywłaszczający w sposób przypadkowy zatrzymuje działanie wątków i przydziela czas procesora innym wątkom, WaŜne jest aby upewnić się, Ŝe wszystkie wątki zatrzymują się periodycznie, po to by inne wątki miały okazję zadziałać. 9 Wielowątkowość - wywłaszczenie Wątek moŜe się zatrzymać lub ustąpić miejsca innym wątkom poprzez: zablokowanie wejścia i wyjścia, zablokowanie obiektu synchronizowanego, rezygnację, przejście w stan uśpienia, połączenie z innym wątkiem, czekanie na obiekcie, skończenie działania, wywłaszczenie przez inny wątek o wyŜszym priorytecie, zatrzymanie. 10 Wielowątkowość - zablokowanie Zablokowanie występuje wtedy gdy wątek musi się zatrzymać by poczekać na zasób, którego nie posiada. Najczęściej wątki blokują się na elementach wejścia/wyjścia systemu (czekanie na przybycie danych z sieci). Blokada występuje takŜe podczas próby dostępu do bloków lub metod synchronizowanych, na których jest załoŜona blokada. 11 Wielowątkowość - rezygnacja Wątek moŜe oddać sterowanie poprzez jawną rezygnację. SłuŜy do tego statyczna metoda klasy Thread: public static void yield() Wątek wywołując tą metodę, sygnalizuje wirtualnej maszynie Javy, Ŝe moŜe ona zacząć inny wątek, jeśli jest gotowy do uruchomienia. Wątek podczas rezygnacji nie powinien być na niczym zsynchronizowany (rezygnacja wątku nie zwalnia Ŝadnych blokad naleŜących do wątku). 12 Wielowątkowość – stan uśpienia Stan uśpienia to forma rezygnacji z zasobów systemu na pewien ustalony czas. Wątek przechodzący w stan uśpienia podobnie jak podczas rezygnacji zatrzymuje wszystkie swoje blokady, więc nie powinien on tego robić w blokach zsynchronizowanych. Odpowiednie metody z klasy Thread to: public static void sleep(long milliseconds) throws InterruptedException public static void sleep(long milliseconds, int nanoseconds) throws InterruptedException Nie ma gwarancji, Ŝe wątek będzie „spał” dokładnie tyle, ile chcemy. Po pierwsze nie wiadomo czy wirtualna maszyna Javy na danym systemie ma moŜliwość odmierzenia czasu z dokładnością do nanosekund czy milisekund. Po drugie po przebudzeniu wątku, moŜe on czekać w kolejce na przydzielenie czasu procesora. 13 Wielowątkowość – stan uśpienia MoŜliwe jest przedwczesne przebudzenie uśpionego wątku poprzez wywołanie metody: public void interrupt() Wywołanie tej metody dla wątku ustawia status przerwania (interrupted status) wątku na true. Wywołanie tej metody w przypadku uśpionego wątku powoduje przerwanie snu wątku i wyrzucenie wyjątku InterruptedException. Status przerwania wątku nadal pozostaje ustawiony na false. 14 Wielowątkowość – stan uśpienia public class Watek { public static void main(String[] args) { System.out.println("Start programu"); MyThread watek1 = new MyThread(1); MyThread watek2 = new MyThread(2); watek1.start(); watek2.start(); } } class MyThread extends Thread { int nr; MyThread(int i) { nr=i; } Start watek watek watek watek watek watek watek watek watek watek watek watek programu 1: 0 2: 0 1: 1 2: 1 1: 2 2: 2 1: 3 2: 3 1: 4 2: 4 1: 5 2: 5 public void run() { for(int i=0 ; i<10; i++){ System.out.println("watek " + nr + ": " + i); try { this.sleep(100); } catch(InterruptedException ie) { System.out.println("Nastąpiło przerwanie snu wątku " + nr); } } } } 15 Wielowątkowość – łączenie z innym wątkiem Łączenie wątków jest przydatne gdy jednemu wątkowi potrzebny jest wynik pracy drugiego wątku. Do łączenia wykorzystuje się następujące metody klasy Thread: public final void join() throws InterruptedException public final void join(long milliseconds) throws InterruptedException public final void join(long milliseconds, int nanoseconds) throws InterruptedException 16 Wielowątkowość – łączenie z innym wątkiem public class Watek { public static void main(String[] args) { try{ System.out.println("start programu"); MyThread watek = new MyThread(); watek.start(); System.out.println("Kolejna instrukcja"); watek.join(); System.out.println("Nastepna instrukcja"); } catch(InterruptedException e) {} } } class MyThread extends Thread { public void run() { for(int i=0 ; i<10; i++){ System.out.println("watek: " } } } start programu Kolejna instrukcja watek: 0 watek: 1 watek: 2 watek: 3 watek: 4 watek: 5 watek: 6 watek: 7 watek: 8 watek: 9 Nastepna instrukcja + i); 17 Wielowątkowość – czekanie na obiekcie MoŜliwe jest czekanie na pewnym obiekcie przez dany wątek. Do czekania na obiekcie moŜna uŜyć metod klasy Object: public final void wait() throws InterruptedException public final void wait(long milliseconds) throws InterruptedException public final void wait(long milliseconds, int nanoseconds) throws InterruptedException 18 Wielowątkowość – czekanie na obiekcie Wątek czeka na obiekcie dopóki: nie upłynie czas oczekiwania, wątek nie zostanie przerwany, obiekt nie zostanie powiadomiony. 19 Wielowątkowość – czekanie na obiekcie Powiadomienie obiektu następuje wtedy, gdy jakiś inny wątek wywoła na nim metodę klasy Object: public final void notify() public final void notifyAll() Metoda notify() w dość losowy sposób wybiera jeden z listy wątków czekających na obiekcie i go budzi. Metoda notifyAll() budzi wszystkie wątki czekające na danym obiekcie. 20 Wielowątkowość – zatrzymanie Gdy metoda run() kończy swoje działanie, wątek umiera. NaleŜy pamiętać by nie przesadzać z tworzeniem wątków, nie tworzyć ich bez potrzeby. Jeśli działanie funkcji run() jest bardzo proste, krótkie i moŜe je zrealizować wątek główny programu to lepiej nie tworzyć wątku. Utworzenie wątku wiąŜe się z pewnym narzutem czasu działania, więc jest wysoko prawdopodobne, Ŝe wykonanie tych samych operacji w głównym wątku będzie szybsze niŜ w oddzielnym wątku. 21 import java.net.*; import java.io.*; import java.util.*; public class Server { public static void main(String[] args) { Server me = new Server(); me.echo("192.168.0.2"); } public void echo(String host){ try{ InetAddress my = InetAddress.getByName("192.168.0.2"); ServerSocket serw = new ServerSocket(1090); while(true){ Socket sock = serw.accept(); new Kl(sock).start(); } } catch(IOException e){ e.printStackTrace(); } finally { (...) } } } class Kl extends Thread { Socket klient; Kl(Socket sock){ this.klient = sock; } public void run(){ try{ ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(klient.getOutputStream())); out.writeDouble(23.54); out.flush(); klient.close(); } catch(IOException e){ e.printStackTrace(); } finally { (...) } 22 } } Klasa File Klasa File reprezentuje nazwę konkretnego pliku albo nazwę zbioru plików w katalogu. Najczęściej uŜywanym konstruktorem klasy File jest: public File(String pathname) throws NullPointerException gdzie pathname to ścieŜka dostępu do pliku lub katalogu. Konstruktor wyrzuci wyjątek NullPointerException jeśli pathname jest null-em. Kolejnymi konstruktorami są: public File(String parent, String child) throws NullPointerException public File(File parent, String child) throws NullPointerException tworzą one ścieŜkę do pliku lub katalogu na podstawie istniejącej ścieŜki parent i nowej ścieŜki child (konkatenacja). Jeśli parent jest null-em – nie jest brany pod uwagę, jeśli child jest null-em - wyrzucany jest wyjątek 23 NullPointerException. Klasa File Istnieje wiele uŜytecznym metod klasy File, oto niektóre z nich: public public public public public public public public public public public public public boolean canRead() boolean canWrite() boolean createNewFile() boolean delete() boolean exists() String getPath() boolean isDirectory() boolean isFile() boolean isHidden() long lastModified() long length() String[] list() boolean mkdir() 24 Odczyt/zapis z/do pliku W Javie odczyt i zapis do plików realizowany jest na zasadzie strumieni. Po otwarciu pliku do odczytu, cała jego zawartość jest dostępna jako strumień. Odczytując z niego dane, „wyciągamy” je bajt po bajcie ze strumienia. Jeśli chcemy pisać do pliku to teŜ konstruujemy odpowiedni strumień i piszemy na nim, a dane trafiają do pliku. Standardowo czytanie i zapis do pliku odbywa się bajt po bajcie. Do odczytu pojedynczych bajtów słuŜy klasa FileInputStream, a do zapisu FileOutputStream. 25 Odczyt z pliku FileInputStream fis = null; try { fis = new FileInputStream("pliktestowy.txt"); int bajt = 0; while((bajt = fis.read()) != -1){ System.out.print((char)bajt); } catch (IOException ex){ System.out.println("Błąd przy odczycie danych: " + ex); } finally { (...) } 26 Odczyt z pliku Java, oprócz czytania bajt po bajcie, oferuje takŜe moŜliwość odczytu całej linii do obiektu typu String. MoŜliwe jest to dzięki tzw. strumieniom buforowanym. Najpierw trzeba utworzyć obiekt FileReader, a następnie trzeba stworzyć obiekt BufferedReader, podając mu obiekt FileReader jako argument: String linia = null; BufferedReader br = null; try { br = new BufferedReader(new FileReader("plik_testowy.txt"); while((linia = br.readLine()) != null){ System.out.println(linia); } } catch (IOException ex){ System.out.println("Błąd przy operacji na pliku: "+ex); } finally { (...) } 27 Zapis do pliku FileOutputStream fos = null; try{ String str = "Próba zapisu"; fos = new FileOutputStream("plik_zapis.txt"); for(int i = 0; i < str.length(); i++){ fos.write((int)str.charAt(i)); } } catch(IOException ex) { System.out.println("Błąd operacji na pliku: " + ex); } finally { (...) } 28 Zapis do pliku Podobnie, aby móc buforować zapis do pliku moŜna posłuŜyć się klasami PrintWriter, BufferedWriter i FileWriter: PrintWriter pw = null; try{ String str = "Próba zapisu"; pw = new PrintWriter(new BufferedWriter(new FileWriter("plik_zapis.txt"))); pw.write(str); pw.flush(); } catch(IOException ex) { System.out.println("Błąd operacji na pliku: " + ex); } finally { (...) } 29 Kolekcje Kolekcje to implementacje struktur danych, słuŜących do przechowywania, przeglądania i zarządzania grupami obiektów Dobór odpowiedniej kolekcji zaleŜy od rozwiązywanego problemu: Czy będziemy chcieli przeszukiwać zbiór składający się z tysięcy czy nawet milionów elementów? Czy niezbędne będzie uporządkowanie tych elementów, moŜliwość łatwego wstawiania, usuwania elementów? Czy elementy mają być szybko wyszukiwane? Kolekcje w Javie rozdzielone są na: interfejsy – definiują abstrakcyjne właściwości i operacje kolekcji, w oderwaniu od konkretnych implementacji implementacje – klasy będące implementacjami konkretnych interfejsów 30 Interfejs Collection Interfejs Collection reprezentuje dowolną grupę obiektów. public interface Collection { boolean add (Object o); boolean contains(Object o); boolean isEmpty(); boolean remove(Object o); int size(); Iterator iterator(); ... } Collection Set Object first(); Object last(); ... List boolean add(int index, Object o); Object remove(int index); Object set(int index, Object o); ... SortedSet 31 Interfejsy Set, SortedSet, List Interfejs Set implementują zbiory, czyli struktury danych, nie zawierające dublujących się elementów. Bardziej formalnie zbiór nie moŜe zawierać dwóch obiektów p1 i p2, dla których p1.equals(p2) zwróci wartość true. Zbiór moŜe zawierać co najwyŜej jeden obiekt null. Klasy implementujący ten interfejs to: HashSet, LinkedHashSet, TreeSet Interfejs SortedSet jest podinterfejsem interfejsu Set i gwarantuje poruszanie się po kolekcji w sposób uporządkowany. Obiekty przechowywane w kolekcjach implementujących ten interfejs powinny implementować interfejs Comparable. Klasa implementująca ten interfejs to: TreeSet Interfejs List implementują listy, czyli struktury danych, w których elementy mają z góry ustalone miejsce. Elementy mogą się dublować. Klasy implementujące ten interfejs to: ArrayList, LinkedList 32 Interfejs Map Interfejs Map reprezentuje grupę odwzorowań klucz i wartość. Klucze nie mogą być zdublowane. Klasy które implementują ten interfejs to: HashMap, Hashtable, TreeMap. public interface Map { boolean containsKey (Object key); boolean containsValue(Object value); Object get(Object key); boolean isEmpty(); Object put(Object key, Object value) Object remove(Object key); int size(); Collection values(); ... } Map SortedMap Object firstKey(); Object lastKey(); ... 33 Interfejs SortedMap Interfejs SortedMap reprezentuje mapy, które gwarantują sortowanie kluczy, według porządku wprowadzonego poprzez implementacje interfejsu Comparable dla obiektu kluczy lub poprzez podanie klasy komparatora przy tworzeniu mapy (który będzie uŜywany do sortowania kluczy). Klasa komparatora to klasa implementująca interfejs Comparator. Klasa implementująca interfejs SortedMap to TreeMap 34 Interfejs Iterator UmoŜliwia poruszanie się po dowolnej klasie implementującej interfejs Collection public interface Iterator { boolean hasNext(); Object next(); void remove(); } • • • Powtarzając wywołanie metody next() dla iteratora moŜemy odwiedzić kolejne obiekty kolekcji. Jeśli dotrzemy w ten sposób do końca kolekcji, to wywołanie metody next() spowoduje wyrzucenie wyjątku NoSuchElementException. Dlatego teŜ wywołanie metody next() nalezy poprzedzić wywołaniem metody hasNext() Kolejność odwiedzania elementów zaleŜy od typu kolekcji. Jeśli odwiedzamy elementy kolekcji ArrayList, to iterator rozpoczyna działanie od elementu o indeksie 0 i następnie zwiększa wartość indeksu o 1. Dla kolekcji HashSet elementy będą odwiedzane w 35 przypadkowej kolejności Interfejs Iterator MoŜemy być pewni, Ŝe uŜywając iteratora, odwiedzimy wszystkie elementy kolekcji, ale nie wolno nam czynić załoŜeń odnośnie ich uporządkowania Iteratory w Javie powinniśmy sobie wyobraŜać jako znaczniki ustawione zawsze pomiędzy elementami kolekcji (jeśli powołujemy iterator to jest on ustawiony przed elementami kolekcji). Wywołanie metody next() powoduje, Ŝe iterator przesuwa się ponad kolejnym elementem kolekcji i zwraca referencję do niego 36 Wykaz najwaŜniejszych implemenatcji kolekcji Typ kolekcji Opis ArrayList sekwencja indeksowana o zmiennych rozmiarach (odpowiednik tablicy) LinkedList sekwencja uporządkowana umoŜliwiająca efektywne wstawianie i usuwanie elementów (odpowiednik listy) HashSet zbiór nieuporządkowany (nie dopuszcza duplikatów) TreeSet zbiór uporządkowany LinkedHashSet zbiór zapamiętujący kolejność wstawiania elementów HashMap mapa (asocjacje klucz/wartość) TreeMap mapa o kluczach uporządkowanych LinkedHashMap mapa zapamiętująca porządek, w którym umieszczane były asocjacje 37 Przykłady uŜycia ArrayList import java.awt.Point; import java.util.ArrayList; import java.util.Iterator; public class TestArrayList { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(new Point(1,1)); list.add(new Point(1,2)); list.add(new Point(1,3)); list.add(1, new Point(2,4)); Iterator i = list.iterator(); while (i.hasNext()) { System.out.println((Point)i.next()); } list.remove(3); list.set(1, new Point(3,3)); System.out.println("==po modyfikacjach=="); i = list.iterator(); while (i.hasNext()) { System.out.println((Point)i.next()); } } } java.awt.Point[x=1,y=1] java.awt.Point[x=2,y=4] java.awt.Point[x=1,y=2] java.awt.Point[x=1,y=3] ==po modyfikacjach== java.awt.Point[x=1,y=1] java.awt.Point[x=3,y=3] java.awt.Point[x=1,y=2] 38 Przykłady uŜycia HashMap import import import import java.util.HashMap; java.util.Iterator; java.util.Map; java.util.Set; public class Test2{ public static void main(String[] args) { HashMap map = new HashMap(); map.put(new Integer(1), "Marcin"); map.put(new Integer(2), "Ada"); map.put(new Integer(4), "Weronika"); map.put(new Integer(3), "Krzysiek"); Set set = map.entrySet(); Iterator i = set.iterator(); while (i.hasNext()) { Map.Entry m = (Map.Entry)i.next(); System.out.println((Integer)m.getKey() } map.remove(new Integer(2)); if (!map.containsKey(new Integer(5))) { map.put(new Integer(5), "Pucio"); } else { System.out.println("Klucz juz istnieje } System.out.println("==po modyfikacjach=="); i = map.entrySet().iterator(); while (i.hasNext()) { Map.Entry m = (Map.Entry)i.next(); System.out.println((Integer)m.getKey() } if (map.containsKey(new Integer(1))) { System.out.println((String)map.get(new } } } 2 -> Ada 4 -> Weronika 1 -> Marcin 3 -> Krzysiek ==po modyfikacjach== 4 -> Weronika 1 -> Marcin 3 -> Krzysiek 5 -> Pucio Marcin + " -> " + (String)m.getValue()); w mapie"); + " -> " + (String)m.getValue()); Integer(1))); 39