Fejlett Programozási Technikák 2. 14/6
A mai előadás tartalma: Többszálúság I/O
Szál (thread) Egyszerű szekvenciális parancssor a programon belül. Könnyűsúlyú folyamat: megosztva használja a kód és az adat szekciót csökken a ”context-switch” végrehajtási idő Segítségével egyszerre több feladatot végezhetünk: képernyő frissítés hálózat kezelés …
Timer I. segítségével egy párhuzamos szálat indíthatunk mely adott időközönként lefuttatja a run metódust az alosztályt a Timer osztályból kell származtatnunk meg kell valósítanunk a run metódust amikor példányosítjuk akkor létrehozunk egy új szálat is, mely a schedule metódus segítségével elindítjuk
Timer II. System.out.println("Time's up!"); timer.cancel(); class RemindTask extends TimerTask { public void run() System.out.println("Time's up!"); timer.cancel(); } public static void main(String args[]) System.out.println("About to schedule task."); new Reminder(5); System.out.println("Task scheduled."); import java.util.Timer; import java.util.TimerTask; public class Reminder { Timer timer; public Reminder(int seconds) timer = new Timer(); timer.schedule(new RemindTask() , seconds*1000); }
Timer III. az időzítésre megadhatunk konkrét időpontot is: Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 1); calendar.set(Calendar.SECOND, 0); Date time = calendar.getTime(); timer = new Timer(); timer.schedule(new RemindTask(), time);
A Timer szál leállítása a cancel metódus meghívásával amennyiben daemon-ként futtatjuk akkor a program automatikusan, befejezi futását ha csak daemon szálak vannak (new Timer(true).). miután minden feladatát végrehajtotta, megszüntetve minden referenciát a Timer objektumra automatikusan befejezi ténykedését a System.exit metódus meghívásával az egész programból kiléphetünk
Példa: class RemindTask extends TimerTask import java.util.Timer; { int numWarningBeeps = 3; public void run() if (numWarningBeeps > 0) { toolkit.beep(); System.out.println("Beep!"); numWarningBeeps--; } else { toolkit.beep(); System.out.println("Time's up!"); System.exit(0); } } } public static void main(String args[]) { System.out.println("About to schedule task."); new AnnoyingBeep(); System.out.println("Task scheduled."); } } import java.util.Timer; import java.util.TimerTask; import java.awt.Toolkit; public class AnnoyingBeep { Toolkit toolkit; Timer timer; public AnnoyingBeep() { toolkit = Toolkit.getDefaultToolkit(); timer = new Timer(); timer.schedule(new RemindTask(), 0, 1*1000); }
Timer, schedule schedule( TimerTask feladat, long késleltetés, long periódus) schedule( TimerTask feladat, Date időpont, long periódus) scheduleAtFixedRate(TimerTask feladat, long késleltetés, long periódus) scheduleAtFixedRate(TimerTask feladat, Date első időpont, long periódus)
Thread osztály a Thread osztály egy általános szálat valósít meg mely nem csinál semmit a run metódus üres a run metódus felülírásával tudunk feladatot specifikálni két módon tudunk szálat megvalósító osztályt definiálni: a Thread osztályból származtatva és felülírva a run metódust megvalósítva a Runnable interfészt
A run metódus felülírása public class SimpleThread extends Thread { public SimpleThread(String str) { super(str); } public void run() for (int i = 0; i < 10; i++) System.out.println(i + " " + getName()); try { sleep((long)(Math.random() * 1000)); } catch (InterruptedException e) {} System.out.println("DONE! " + getName()); } }
Futtatás 6 Fiji 0 Jamaica 7 Fiji 0 Fiji 5 Jamaica 1 Jamaica 8 Fiji DONE! Fiji 8 Jamaica 9 Jamaica DONE! Jamaica 0 Jamaica 0 Fiji 1 Jamaica 1 Fiji 2 Fiji 2 Jamaica 3 Fiji 3 Jamaica 4 Jamaica 4 Fiji 5 Fiji public class TwoThreadsDemo { public static void main (String[] args) new SimpleThread("Jamaica").start(); new SimpleThread("Fiji").start(); }
A Runnable interfész megvalósítása public class SimpleThread implements Runnable { Thread SimpleThread; public SimpleThread(String str) { SimpleThread = new Thread(this, str);} public void start() {SimpleThread.start(); } public void run() { for (int i = 0; i < 10; i++) { System.out.println(i + " " + myThread.getName()); try { myThread.sleep((long)(Math.random() * 1000)); } catch (InterruptedException e) {} } System.out.println("DONE! " + myThread.getName());
Mikor melyik módot válasszuk ? Amennyiben osztályunknak származnia kell valahonnan akkor az interfész a megoldás (pl Applet esetén) Egyébként tetszőleges
A szál életciklusa done sleeping blokkolt suspend sleep új resume start futtatható wait notify run metódus kilép halott stop
Állapotok I. új szál runnable amikor a new operátorral létrehozzuk még nem fut ekkor a new állapotban van runnable a megfelelő erőforrások lefoglalását, a szál adminisztrálását, a szál futását a start metódus segítségével tudjuk elindítani a new állapotból. ebben az állapotban a szál nem feltétlenül fog futni. Az operációs rendszer feladata a megfelelő futási idő biztosítása számára. a szálak közötti váltás operációs rendszer függő (pl.: solaris esetén green threads, windows esetén mindegyik szálnak egy időszelet)
Állapotok II. blokkolt szálak a következő módokon kerülhetnek ebbe az állapotba: a sleep() metódus eredményeként (ekkor adott ideig lesz itt) egy olyan művelet végrehajtása esetén mely input/output műveletekkel blokkolt és addig nem tér vissza míg ezek be nem fejeződtek a wait() metódus meghívására (másik szálnak meg kell hívnia a notify vagy notifyAll metódust) amikor olyan objektumhoz próbál hozzáférni amely zárolt a suspend() metódus meghívására (ez már elavult) a resmue() metódussal lehet visszahozni. futtatható állapotba csak úgy kerülhetnek vissza ha a blokkolást előidéző eseménynek megfelelő esemény következik be
Állapotok III. halott szálak a run metódus kilép hirtelen meghal mert egy kezeletlen kivétel miatt megszűnik a run metódus a stop metódus elavult ezért ne használjuk az isAlive metódus segítségével nézhetjük meg, hogy él-e még.
A run metódus kezelése I. a feladat befejezése után kilép: public void run() { for (int i = 0; i < 10; i++) System.out.println(i + " " + getName()); try { sleep((long)(Math.random() * 1000)); } catch (InterruptedException e) {} } System.out.println("DONE! " + getName()); } }
A run metódus kezelése II. Figyeli, hogy ki kell-e lépnie: public void run() { Thread myThread = Thread.currentThread(); while (clockThread == myThread) { repaint(); try { Thread.sleep(1000); } catch (InterruptedException e){} } public void stop() { clockThread = null;
Prioritás a szálak versenyeznek a CPU használatáért nagyobb prioritású szálak előnyben vannak a kisebb prioritású szálakkal szemben a kisebb prioritású szálak csak akkor jutnak CPU időhöz, ha a nagyobb prioritású szálak már nem tartanak igényt a CPU-ra az egyenlő prioritású szálak kezelése operációs rendszer függő win9x, WinNT… az egyenlő prioritású szálak felváltva kapnak CPU időt (időosztás) solaris az egyenlő prioritású szálak csak között csak a feladat befejezése után történik váltás (probléma az önző szálak kezelése) a setPriority() metódussal lehet beállítani egy szál prioritását (MAX_PRIOROTY=10, MIN_PRIORITY=1)
Példa: public class SimpleThread implements Runnable { Thread SimpleThread; public SimpleThread(String str) { SimpleThread = new Thread(this, str); } public void start() SimpleThread.start(); public void setPriority(int i) SimpleThread.setPriority(i); public void run() { Thread myThread = Thread.currentThread(); for (int i = 0; i < 10; i++) for (int c = 0; c < 1000000; c++); System.out.println(i + " " + SimpleThread.getName()); System.out.println("DONE! " + SimpleThread.getName()); Példa:
Egyforma prioritás public class ThreeThreadsTest { 0 Kolumbia 1 Kolumbia 2 Kolumbia 0 Jamaica 1 Jamaica 2 Jamaica 3 Jamaica 4 Jamaica 3 Kolumbia 4 Kolumbia 5 Kolumbia 6 Kolumbia 5 Jamaica 6 Jamaica 7 Jamaica 8 Jamaica 9 Jamaica 7 Kolumbia 8 Kolumbia 9 Kolumbia DONE! Kolumbia DONE! Jamaica public class ThreeThreadsTest { public static void main (String[] args) { SimpleThread t; SimpleThread t1; t = new SimpleThread("Jamaica"); t.setPriority(1); t1 = new SimpleThread("Kolumbia"); t1.setPriority(1); t1.start(); t.start(); }
Különböző prioritás public class ThreeThreadsTest { 0 Jamaica 1 Jamaica 2 Jamaica 3 Jamaica 4 Jamaica 5 Jamaica 6 Jamaica 7 Jamaica 8 Jamaica 9 Jamaica DONE! Jamaica 0 Kolumbia 1 Kolumbia 2 Kolumbia 3 Kolumbia 4 Kolumbia 5 Kolumbia 6 Kolumbia 7 Kolumbia 8 Kolumbia 9 Kolumbia DONE! Kolumbia public class ThreeThreadsTest { public static void main (String[] args) { SimpleThread t; SimpleThread t1; t = new SimpleThread("Jamaica"); t.setPriority(10); t1 = new SimpleThread("Kolumbia"); t1.setPriority(1); t1.start(); t.start(); }
Megjegyzés olyan szálakat kell írnunk melyek nem önzőek az operációs rendszerek különbözőek, ha platform független programot akarunk írni akkor nekünk kell gondoskodnunk a megfelelő időosztásról a yield() metódus segítségével tudjuk egy szál futását felfüggeszteni és a CPU-t átadni egy legalább ilyen prioritású szálnak (solaris) nem minden operációs rendszer rendelkezik 10 prioritási szinttel (pl winnt 7) a szálak prioritásukat a létrehozó száltól öröklik
Szálak szinkronizálása Egyes feladatokat nem lehet megoldani egymástól független szálakkal (pl.: közös erőforrás használata módosítása) Hibás működéshez vezethet amennyiben nem gondoskodunk zárolásról a közös erőforrás használatakor pl.: két vagy több szál bankbetétek között végez tranzakciókat. A művelet bármikor megszakadhat (windows) és nem konzekvens állapot maradhat, amit egy másik szál felhasználhat. a problémát az adatmódosító metódus megszakíthatósága okozza
Zárolás synchronized a zárolt objektumhoz csak egy szál férhet hozzá ha egy szál használ egy zárolt objektumot és meghív egy másik szálat akkor a másik szál hozzáférhet az objektumhoz minden objektumnak van egy zárolás számlálója egy szál egyszerre több objektumot is zárolhat a szál ütemező periodikusan aktiválja a szálakat, azok melyek egy zárolt objektumra várakoznak a következő soron kívüli futási jogot kapnak.
wait, notify, notifyAll előfordulhat olyan eset amikor egy szál a másik akciójára vár ilyenkor mivel a közös objektumot zárolt függvényeken keresztül lehet elérni ez végtelen ciklushoz vezetne wait – ha egy szál meghívja a wait() metódust akkor az objektum felszabadul és a szál blokkolva lesz amíg az adott objektumnál egy másik szál meg nem a hívja a notify, notifyAll metódust érdemes a notifyAll metódust használni mivel a másik a blokkolt szálak közül csak egyet szabadít fel azt is véltelenszerűen
Példa: Tároló objektum: Tarolo Adat forrás: Producer Adat nyelő: Consumer main: PTest
Tarolo.java public class Tarolo { private int contents; private boolean available = false; public synchronized int get() { while (available == false) { try { wait(); } catch (InterruptedException e) { } } System.out.println("Olvasgatok"); available = false; notifyAll(); return contents; public synchronized void put(int value) while (available == true) { System.out.println("Irogatok"); contents = value; available = true; notifyAll(); Tarolo.java
public class Producer extends Thread { private Tarolo tarolo; private int number; public Producer(Tarolo c, int number) { tarolo = c; this.number = number; } public void run() { for (int i = 0; i < 10; i++) { tarolo.put(i); System.out.println("Producer #" + this.number + " beirtam: " + i); try { sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { }
public class Consumer extends Thread { private Tarolo tarolo; private int number; public Consumer(Tarolo c, int number) { tarolo = c; this.number = number; } public void run() { int value = 0; for (int i = 0; i < 10; i++) { value = tarolo.get(); System.out.println("Consumer #" + this.number + " kiolvastam: " + value);
Main public class PTest { public static void main(String[] args) { Tarolo c = new Tarolo(); Producer p1 = new Producer(c, 1); Consumer c1 = new Consumer(c, 1); c1.start(); p1.start(); }
Eredmény H:\>java PTest Irogatok Olvasgatok Consumer #1 kiolvastam: 0 Producer #1 beirtam: 0 …. Producer #1 beirtam: 8 Consumer #1 kiolvastam: 8 Producer #1 beirtam: 9 Consumer #1 kiolvastam: 9
Újrafoglalás public class Reentrant { public synchronized void a() { System.out.println("here I am, in a()"); } public synchronized void b() { System.out.println("here I am, in b()");
Deadlock, éhezés ebédelő filozófusok nehéz észlelni a végtelen ciklusokat célszerű olyan szabályokat alkal- mazni melyekkel liküszöbölhetőek
Szál csoportok a szálakat csoportokba tudjuk foglalni a csoportokon egyszerre tudunk műveleteket végrehajtani egy szál a létrehozásakor helyezhető egy csoportba, ezután már nem mozdítható el ha nem adunk meg mást akkor automatikusan a szülő szál csoportjába kerül új csoportokat létrehozása esetén az új csoport a szülő csoport gyerek csoportja lesz
Szál csoport metódusok ThreadGroup(Sting name) – konstruktor ThreadGroup(ThreadGroup parent ,Sting name) – konstruktor, itt az ős a parent csoport int activeCount() – a csoportban lévő aktív szálak számát adja vissza int enumerate(Thread[] list) – a csoportban lévő szálak hivatkozásait gyűjti össze TheradGroup getParent() – a szülő csoportot adja vissza void interrupt() – megszakítja minden szál munkáját
I/O írás, olvasás az információ írásához olvasásához csövekre van szükségünk java.io: karakter folyamok bájt folyamok
I/O – karakter folyamok 16 bites karakterek olvassák, írják a forrást valamilyen műveleteket végeznek rajta
I/O – bájt folyamok 8 bites egységek egyszerű folyamok feladat végző folyamok
Fájl folyamok FileReader FileWriter FileInputStream FileOutputStream public class Copy { public static void main(String[] args) throws IOException { File inputFile = new File("farrago.txt"); File outputFile = new File("outagain.txt"); FileReader in = new FileReader(inputFile); FileWriter out = new FileWriter(outputFile); int c; while ((c = in.read()) != -1) out.write(c); in.close(); out.close(); } FileReader FileWriter FileInputStream FileOutputStream
Sor folyamok (Pipe streams) Arra szolgálnak, hogy összekössék egyik szál bemenő csatornáját a másik szál kimenő csatornájával. Az egyik szál beírja az adatokat a csatornába és nem foglalkozik vele tovább A másik kiolvassa Nem kell foglalkoznunk a szinkronizálással Metódusai: PipedReader PipedWriter PipedInputStream PipedOutputStream
Példa: RhymingWords ReverseThread SortThread
import java.io.*; public class RhymingWords { public static void main(String[] args) throws IOException { FileReader words = new FileReader("words.txt"); Reader rhymedWords = reverse(sort(reverse(words))); BufferedReader in = new BufferedReader(rhymedWords); String input; while ((input = in.readLine()) != null) System.out.println(input); in.close(); } public static Reader reverse(Reader source) throws IOException { BufferedReader in = new BufferedReader(source); PipedWriter pipeOut = new PipedWriter(); PipedReader pipeIn = new PipedReader(pipeOut); PrintWriter out = new PrintWriter(pipeOut); new ReverseThread(out, in).start(); return pipeIn; . . . folyt köv
public static Reader sort(Reader source) throws IOException { BufferedReader in = new BufferedReader(source); PipedWriter pipeOut = new PipedWriter(); PipedReader pipeIn = new PipedReader(pipeOut); PrintWriter out = new PrintWriter(pipeOut); new SortThread(out, in).start(); return pipeIn; }
import java.io.*; public class ReverseThread extends Thread { private PrintWriter out = null; private BufferedReader in = null; public ReverseThread(PrintWriter out, BufferedReader in) { this.out = out; this.in = in; } public void run() { if (out != null && in != null) { try { String input; while ((input = in.readLine()) != null) { out.println(reverseIt(input)); out.flush(); out.close(); } catch (IOException e) { System.err.println("ReverseThread run: " + e); } … folytatás
private String reverseIt(String source) { int i, len = source.length(); StringBuffer dest = new StringBuffer(len); for (i = (len - 1); i >= 0; i--) dest.append(source.charAt(i)); return dest.toString(); }
DataInputStream, DataOutputStream segítségével egyszerűen tudunk más sorokba olvasni, írni egyedül nem használható mindenképpen csatolni kell egy másik csőhöz
import java.io.*; public class DataIODemo { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream(new FileOutputStream(”a1.txt")); double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 }; int[] units = { 12, 8, 13, 29, 50 }; String[] descs = { "Java T-shirt", "Java Mug", "Duke Juggling Dolls", "Java Pin", "Java Key Chain" }; for (int i = 0; i < prices.length; i ++) { out.writeDouble(prices[i]); out.writeChar('\t'); out.writeInt(units[i]); out.writeChars(descs[i]); out.writeChar('\n'); } out.close(); … folyt köv
DataInputStream in = new DataInputStream(new FileInputStream(”a1 DataInputStream in = new DataInputStream(new FileInputStream(”a1.txt")); double price; int unit; StringBuffer desc; double total = 0.0; try { while (true) { price = in.readDouble(); in.readChar(); // throws out the tab unit = in.readInt(); char chr; desc = new StringBuffer(20); char lineSep = System.getProperty("line.separator").charAt(0); while ((chr = in.readChar()) != lineSep) desc.append(chr); System.out.println("You've ordered " + unit + " units of " + desc + " at $" + price); total = total + unit * price; } } catch (EOFException e) {} System.out.println("For a TOTAL of: $" + total); in.close();
Véletlen hozzáférésű fájlok előfordul, hogy a fájl tartalmát nem sorban kell olvasnunk, hanem véletlen, tetszőleges sorrendben ilyen lehet egy .ZIP fájl melyben megkeressük az adott fájlt és azt kitömörítjük RandomAccessFile osztály: new RandomAccessFile("farrago.txt", "r"); new RandomAccessFile("farrago.txt", "rw"); int skipBytes(int) void seek(long) long getFilePointer()