Konkurentní programování


Materiály

Vlákna vs Procesy

V konkuretním programování máme 2 základní jednotky vykonávání programu: Procesy a Vlákna. Procesy jsou samostané jednotky s vlastní pamětí a zdroji. Dále pak proces nemůže přímo přistupovat ke zdrojům jiného procesu.

Vlákna můžeme nazvat "odlehčené procesy". Vlákna mezi sebou mohou sdílet protředky a paměť. Obecně řečeno se procesy mohou obsahovat více vláken. Vlákno v Javě vytvoříme zděděním trídy Thread nebo implementací rozhraní Runnable. Java aplikace má zpočátku jedno vlákno kterému říkáme hlavní vlákno (Main Thread).

Třída Thread

V Javě máme dvě možnosti jak pracovat s vlákny

  1. Pokud chceme kontrolovat proces vztvoření a řízení vlákna, pak stačí vytvořit instanci třídy Thread pokaždé když bude aplikace potřebovat nové vlákno.
  2. Pro abstraktní práci s vlákny je možné použít tzv. Executors, více zde.
    
    //Vytvoření vlákna pomocí dědění třídy Thread
    public class HelloThread extends Thread {

        public void run() {
            System.out.println("Hello from a thread!");
        }

        public static void main(String args[]) {
            (new HelloThread()).start();
        }
    }

    //Vytvoření nového vlákna implementací rozhraní Runnable
    public class HelloRunnable implements Runnable {

        public void run() {
            System.out.println("Hello from a thread!");
        }

        public static void main(String args[]) {
            (new Thread(new HelloRunnable())).start();
        }
    }
    

Pozastavení vykonávání

Pokud chceme pozastavit výkonávání programu v určitém vlákně, tak k tomu použijeme metodu Thread.sleep. Pomocí této metody zastavíme vykonávání programu na určitou dobu. Čekání není aktivní takže nezatěžuje procesor a ten může být přídelen jinému vláknu.

    
    public class SleepMessages {

        public static void main(String args[]) throws InterruptedException {
            String importantInfo[] = {
                "Mares eat oats",
                "Does eat oats",
                "Little lambs eat ivy",
                "A kid will eat ivy too"
            };

            for (int i = 0; i < importantInfo.length; i++) {
                //Pozastavení programu na 4 sekundy
                Thread.sleep(4000);

                System.out.println(importantInfo[i]);
            }
        }
    }
    

Přerušení vlákna

Každé vlákno může být přerušeno pomocí metody interrupt(). Pokud je vlákno přerušeno tak by mělo ukončít svojí činnost.

    
    public static void main(String[] args) throws IOException, InterruptedException {

        Thread printer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(2000);
                    System.out.println(i + ". Hello World!");
                } catch (InterruptedException e) {
                    System.err.println(e.getMessage());
                    return;
                }

            }
        });

        printer.start();

        Thread.sleep(3000);
        printer.interrupt();
    }

    //Výstup
    // 0. Hello World!
    // sleep interrupted

    

Join

Vlákno může čekat na dokončení jiného vlákna. Čekání se provádí metodou join().

    
    public static void main(String[] args) throws IOException, InterruptedException {

        Thread printer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(2000);
                    System.out.println(i + ". Hello World!");
                } catch (InterruptedException e) {
                    System.err.println(e.getMessage());
                    return;
                }
            }
        });

        printer.start();
        // Hlavní vlákno bude čekat na dokončení vlákna printer
        printer.join();

        System.out.println("Printer Thread complete.");
    }

    

Synchronizace

Pokud více vláken přistupuje ke sdílenému zdroji tak je nutná synchronizace aby s daným zdrojem pracovalo pouze jedno vlákno. K synchronizaci v Javě používáme synchronizované metody nebo bloky. Dále pokud chceme aby k jedne proměnné přistupovalo více vláken tak tuto proměnnou označíme jako volatile.

Vlákno také může čekat pomoci wait(). Tato metoda čeká na zavolaní metody notify() (Probudí 1 čekající vlákno) nebo notifyAll() (Probudí všechna čekající vlákna).

    
    public class SynchronizedCounter {
        private int c = 0;

        public synchronized void increment() {
            c++;
        }

        public synchronized void decrement() {
            c--;
        }

        public synchronized int value() {
            return c;
        }
    }


    //Synchronizovaný blok.

    synchronized(object) {
        // Code
    }

    

Úkoly

  1. Napište program, který bude obsahovat dvě vlákna. Jedno vlákno bude provádět výpočet fibonacciho čísla rekurzivní metodou (zvolte vhodné číslo) a druhé bude na standardní výstup v sekundových intervalech vypisovat, jak dlouho již výpočet běží.

  2. Bonusový

    Napište metodu int parfib(int n, int k), která vypočítá n-té fibonacciho číslo, ale k výpočtu použije až k vláken. Pokud máte možnost, ověřte na víceprocesorovém počítači, že program běží rychleji. Kód okomentujte, aby bylo možné pochopit, jak probíhá paralelizace.

Úkoly posílejte na email r.vyjidacek@gmail.com s předmětem ZP3JV10. Termín odevzdání do půlnoci 6. 12. 2017. Odevzdávejte pouze zdrojové kódy v archivu zip.