Threads, Animation und Images

Inhalt

 

 

Threads

 

Synchronisation

 

Animation

 

Images Laden und Erzeugen

 

Übungsaufgaben

Threads

Threads sind scheinbar parallel arbeitende Teile eines Programs, die auf denselben Datenbestand zugreifen. Bei zwei parallel arbeitenden getrennten Programmen spricht man von Prozessen. Unsere bisherigen Grafikprogramme arbeiteten schon parallel, ohne dass wir das ausgenutzt haben. Der eine Thread war nämlich das Programm main, und der andere war die Ereignisbehandlung. Über den Thread von main hat unser Programm allerdings keine Kontrolle, da es ihn nicht besitzt. So kann man diesen Thread insbesondere nicht unterbrechen oder warten lassen. Der main-Thread endet natürlich, wenn das Unterprogramm main beendet wird. Das Programm selbst kann trotzdem weiter laufen. Erst wenn das letzte Fenster geschlossen wird, endet auch die Ereignisbehandlung, und damit das Programm. Alternativ kann das Programm jederzeit komplett mit System.exit(0) abgebrochen werden.

Eine typische Anwendung für Multi-Threading sind animierte Programme. Wir wollen zuerst ein Beispiel angeben, wo die aktuelle Zeit in einem Fenster tickt. Dazu startet man einen Thread, der im Sekundentakt ein repaint des Applets aufruft.

Der Sekundentakt wird dadurch erzeugt, dass die Methode sleep() von Thread aufgerufen wird. Diese Methode erzeugt eventuell eine Exception (wenn das Unterbrechen nicht möglich war), die abgefangen werden muss.

Würde man einfach in das Fenster zeichnen, dann würde der String jedesmal gelöscht werden, bevor er neu gezeichnet wird. Der Benutzer sieht ein Flimmern. Daher verwenden wir einen Double Buffer. Es wird im Prinzip ein Image angelegt, auf das gezeichnet wird, und dann erst das Image auf den Schirm kopiert. Java erledigt das automatisch für JPanel, wenn setDoubleBuffered(true) gesetzt ist. Man muss dann aber den Hintergrund selbst löschen.

 

import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Date;

import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * Ein Thread, der alle 1000 msec aktiv wird.
 */
class RedrawThread extends Thread
{
    JPanel canvas;
    boolean stop = false;

    public RedrawThread (JPanel canvas)
    {
        this.canvas = canvas;
        start();
    }

    @Override
    public void run ()
    {
        while (!stop)
        {
            try // Notwendig!
            {
                sleep(1000); // Warte 1 Sekunde
            }
            catch (Exception ex)
            {
            }
            canvas.repaint();
        }
    }
}

class Display extends JPanel
{
    public Display ()
    {
        setPreferredSize(new Dimension(300, 100));

        // Setze Panel auf Double Buffered
        setDoubleBuffered(true);
    }

    @Override
    public void paint (Graphics g)
    {
        g.setFont(new Font("Dialog", Font.BOLD, 16));
        FontMetrics metrics = g.getFontMetrics();
        String s = new Date().toString();

        // Wegen Double Buffer müssen wir selber löschen
        int w = getWidth();
        int h = getHeight();
        g.clearRect(0, 0, w, h);

        // Berechne Stringbreite und -höhe
        int ws = metrics.stringWidth(s);
        int hs = metrics.getHeight();

        // Berechne Aufpunkt für String
        int x = (w - ws) / 2;
        int y = (h - hs) / 2 + metrics.getAscent();

        g.drawString(s, x, y);
    }
}

public class Test extends JFrame
{
    RedrawThread redraw;

    public Test ()
    {
        super("Clock");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setIconImage(new javax.swing.ImageIcon(
                getClass().getResource("hearts32.png")).getImage());

        Display display = new Display();
        add("Center", display);

        redraw = new RedrawThread(display);

        addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing (WindowEvent e)
            {
                redraw.stop = true;
            }
        });

        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main (String args[])
    {
        new Test();
    }
}

Man sollte den Thread unterbrechen, wenn das Fenster geschlossen wurde. Dazu bekommt der Thread eine Variable, die ihn zum Anhalten motiviert. Diese Variable wird gesetzt, wenn der Frame geschlossen wird. Ein direktes Unterbrechen des Threads wäre nicht sicher und ist in Java nicht möglich.

Zum Zentrieren der Zeitdarstellung wurde wieder eine FontMetrics verwendet. Außerdem haben wir wieder ein Icon für das Fenster gesetzt.

Synchronisation

Wir weisen an dieser Stelle darauf hin, dass Probleme auftreten können, wenn mehrere Threads auf dasselbe Objekt, oder dieselbe Variable zugreifen. Die Threads können sich nämlich nicht darauf verlassen, dass das Objekt für eine bestimmte Zeitspanne unverändert bleibt. Solchen Problemen muss man mit Synchronisation abhelfen.

Dazu kann man einen Block mit einem Objekt synchronisieren. Innerhalb dieses Blocks hat der Thread dann einen so genannten Lock auf das Objekt. Kein anderer Thread kann in den Block eintreten, bevor das Objekt nicht freigegeben ist. Solch ein Block sieht folgendermaßen aus

synchronized (Objekt)
Anweisungsblock

Alternativ kann man auch eine ganze Methode einer Klasse mit einem Lock auf die Instanz der Klasse, deren Methode aufgerufen wurde, versehen. Dazu erhält die Methode das Schlüsselwort synchronized.

Beispiel

    public synchronized int inc ()
    {   
        int m=n;
        n++;
        return m;
    }

Dieses Beispiel erhöht n und gibt den alten Wert zurück. Falls n Variable des Objektes A ist, so kann A.inc() nur in jeweils einem Thread aktiv sein. Dadurch wird verhindert, dass der Wert von n in A inkonsistent ist. Es ist übrigens immer gewährleistet, dass n++ nicht unterbrochen wird. Aber hier kommt es darauf an, dass der Abruf des alten Wert und die Erhöhung nicht durch Zugriffe anderer Threads unterbrochen werden.

Ein weiteres Problem sind Race Condistions. Man weiß nie, an welcher Stelle der andere Thread ist und ob er gerade gestarted wurde. Womöglich will man ihn unterbrechen, aber er wurde noch nicht einmal gestarted. Das ist auch der Grund, warum der Thread im obigen Beispiel nicht einfach unterbrochen wird, sondern stattdessen eine Variable stop gesetzt wird.

Man kann allerdings mit wait und notify darauf warten, dass der Thread seine Arbeit verrichtet hat. Beide Methoden müssen in einem synchronized-Block aufgerufen werden. Dadurch werden nämlich die Objekte Eigentümer des Threads. Außerdem kann wait eine Exception werfen, die man abfangen sollte.

Die Mechanismen wait und notify sind für synchronisierte Kommunikation zwischen Threads gedacht. Im folgenden Beispiel haben wir einen Producer und einen Consumer.Der Producer produziert hie einfach nur die Zahlen 1 bis 10. Es könnte aber auch eine Eingabe von einem externen Device sein, die weitergegeben werden soll. Wenn er eine Zahl produziert hat, gibt er sie an den Consumer weiter, indem er dort eine Variable ändert. Der Consumer gibt die Zahl einfach aus.

Damit die Synchronisation funktionert, wartet der Consumer mit wait auf den Producer, der nach der Produktion ein notify abschickt. Nachdem die Zahl verarbeitet wurde, wartet dier Consumer mit wait und benachfichtigt den Producer.

class Producer implements Runnable
{
    Consumer consumer;
    boolean ended;

    public Producer (Consumer consumer)
    {
        this.consumer = consumer;
        ended = false;
        new Thread(this).start();
    }

    @Override
    public void run ()
    {
        for (int i = 0; i < 10; i++)
        {
            consumer.current = i;
            System.out.println(i + " produced");
            synchronized (consumer)
            {
                consumer.notify();
            }
            synchronized (this)
            {
                try
                {
                    wait();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }

        ended = true;
    }
}

class Consumer implements Runnable
{
    int current;
    Producer producer;

    public void setProducer (Producer producer)
    {
        this.producer = producer;
        new Thread(this).start();
    }

    @Override
    public void run ()
    {
        while (!producer.ended)
        {
            synchronized (this)
            {
                try
                {
                    wait();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            System.out.println(current + " received");
            synchronized (producer)
            {
                producer.notify();
            }
        }
    }

}

public class Test
{
    public static void main (String args[])
    {
        Consumer consumer = new Consumer();
        Producer producer = new Producer(consumer);
        consumer.setProducer(producer);
    }
}

Das Programm gibt die folgende Ausgabe aus.

0 produced
0 received
1 produced
1 received
2 produced
2 received
3 produced
3 received
4 produced
4 received
5 produced
5 received
6 produced
6 received
7 produced
7 received
8 produced
8 received
9 produced
9 received

Will man mehrere Threads parallel laufen lassen, so bieten sich die Executoren an. Das folgende Programm demonstriert, wie man 2 Threads erzeugt, laufen lässt, und auf das Ende dieser Threads wartet.

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class Task implements Runnable
{

    @Override
    public void run ()
    {
        for (int i = 0; i < 10; i++)
            System.out.println(i);
    }

}

public class Test
{
    public static void main (String args[])
    {
        System.out.println("Main started");

        ThreadPoolExecutor executor = //
                (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

        for (int i = 0; i < 2; i++)
        {
            Task t = new Task();
            executor.execute(t);
        }
        executor.shutdown();

        try
        {
            executor.awaitTermination(10, TimeUnit.SECONDS);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

        System.out.println("Main ended");
    }
}

Die Ausgabe ist wie erwartet.

Main started
0
1
2
3
4
5
6
7
0
1
2
3
4
5
6
7
8
9
8
9
Main ended

Die Threads arbeiten offenbar parallel, da die Zahlen durcheinander ausgegeben werden. Man beachte, dass System.out.println synchronisiert ist. Es kann also auch bei der Ausgabe von längeren Strings nichts passieren. Das Hauptprogramm wird erst beendet, wenn alle Zahlen ausgegeben sind.

Animation

In diesem Abschnitt werden wir ein Programm schreiben, bei dem ein Ball innerhalb der Fläche des Canvas hüpft. Der Ansatz ist, einen separaten Thread laufen zu lassen, der die Position des Balles neu berechnet und den Ball neu zeichnet.

Es sind einige Worte zur Erklärung notwendig, da es sich schon um ein sehr komplexes Programm handelt.

Zur Darstellung verwenden wir wieder ein JPanel mit Double Buffer. Wir könnten auch das Neuzeichnen des Hintergrundes verhindern, indem wir die Methode update(Grahics g) überschreiben und dort lediglich selbst paint(g) aufrufen. Man wird in diesem Beispiel auf einem schnellen Rechner kaum einen Unterschied sehen, da der nur der Ball vom Hintergrund überzeichnet wird. Aber der Double Buffer kostet kaum Rechenzeit.

Statt direkt einen Thread zu erzeugen, ist es logischer, das Interface Runnable zu implementieren. Die Handhabung ist einfacher. Der Thread wird hier gestarted, wenn das Fenster den Fokus bekommt.

Dieses Programm soll die Animation stoppen, wenn das Fenster den Fokus verliert. Die einfachste und sicherste Art ist, den Thread zu stoppen. Wenn der Fokus wieder zurück kommt, starten wir einen neuen Thread, der die Animation am selben Punkt fortsetzt. Es gibt auch Methoden, mit dénen man in run() auf ein Ereignis warten kann, das man selbst erzeugen muss.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;

class BallCanvas extends JPanel implements Runnable
{
    boolean stop;

    int x, y, dx, dy; // Ballkoordinaten von links unten gesehen
    int w, h; // Hoehe und Weite des Applets
    int s; // Kugelgroesse

    /**
     * Initialisieren
     */
    public BallCanvas ()
    {
        setPreferredSize(new Dimension(800, 600));
        x = 400;
        y = 10;
        dx = 2;
        dy = 30;
        s = 20;

        setDoubleBuffered(true);
    }

    /**
     * Zeichne Hintergrund. Zeichne den Ball an der richtigen Stelle.
     */
    @Override
    public void paint (Graphics g)
    {
        w = getWidth();
        h = getHeight();
        g.setColor(Color.blue.darker());
        g.fillRect(0, 0, w, h);
        g.setColor(Color.green.darker());
        g.fillOval(x - s, h - y - s, 2 * s, 2 * s);
    }

    /**
     * Schleife des Threads. Der Ball hüpft mit Schwerkraft und verliert bei
     * jedem Aufprall an Energie, bis er nur noch rollt.
     */
    @Override
    public void run ()
    {
        while (!stop)
        {
            // Ball verschieben:
            if (x + dx - s <= 0)
                dx = -dx;
            if (x + dx + s >= w)
                dx = -dx;
            if (y + dy - s <= 0)
            {
                dy = -dy - 1;
                y = s;
            }
            else
                dy--;
            x += dx;
            y += dy;

            repaint();
            // Etwas verzögern
            try
            {
                Thread.sleep(10);
            }
            catch (Exception e)
            {
            }
        }
    }

}

public class Test extends JFrame implements FocusListener
{
    BallCanvas ball;
    boolean stop;

    public Test ()
    {
        super("Ball Animation");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setIconImage(new javax.swing.ImageIcon(
                getClass().getResource("hearts32.png")).getImage());

        ball = new BallCanvas();
        add("Center", ball);

        addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing (WindowEvent e)
            {
                ball.stop = true;
            }
        });

        addFocusListener(this);

        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main (String args[])
    {
        new Test();
    }

    // Routinen für FocusListener

    @Override
    public void focusGained (FocusEvent e)
    {
        ball.stop = false;
        new Thread(ball).start();

    }

    @Override
    public void focusLost (FocusEvent e)
    {
        ball.stop = true;
    }

}

Images Laden und Erzeugen

Wir möchten nun unseren hüpfenden Ball aufhübschen, indem wir den Hintergrund von einer Datei laden, und den Ball selbst rendern. Das Laden ist einfach, wenn der Pfad zur Datei bekannt ist.

BufferedImage img = null;
try 
    {
        img = ImageIO.read(new File("datei.jpg"));
    } catch (IOException e) {
}

Java-Programme werden aber oft in jar-Dateien gepackt, einschließlich der Images, die dann Resourcen heißen. Der dazu nötige Code ist im Beispiel enthalten.

Der Ball im obigen Beispiel ist ein teilweise transparentes Image, dessen Bild Punkt für Punkt gerendert wurde. Dazu dient die Klasse MemoryImageSource aus dem Paket java.awt.image. Ein Objekt dieser Klasse kann an createImage übergeben werden, um das Image zu erzeugen (in diesem Fall das Image Ball). Ein MemoryImageSource wird aus einzelnen Pixeln erzeugt, die in einem int-Array gespeichert sind. Jedes Pixel besteht aus vier Bytes , die die Transparenz und die Intensität der drei Grundfarben (RGB) für das Pixel angeben (jeweils 0 bis 255, wobei 255 für "nicht transparent" steht). Man setzt das Pixel am besten mit Hilfe der Schiebeoperationen und eines bitweisen Oder (|) zusammen.

Interessant ist noch, wie die Farbanteile für die Pixel bestimmt werden. Dazu wird zunächst die zweidimensionale Aufsichtkoordinate (i,j) der Kugel in die dreidimensionale Oberflächenkoordinate (x,y,z) umgerechnet. Diese wird mit (1,1,1) skalar multipliziert. Der enstandene Wert dient, richtig skaliert, als Helligkeit an dieser Stelle. Dieser kleine mathematische Trick simuliert überzeugend einen Lichteinfall von rechts oben vorne.

Details kann man der Funktion createBall() im folgenden Listing entnehmen.

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.ColorModel;
import java.awt.image.MemoryImageSource;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

class BallCanvas extends JPanel implements Runnable
{
    boolean stop;

    Image I; // Puffer
    Image Back; // Hintergrund
    Image Ball; // Ball
    Graphics G; // Graphics fuer den Puffer

    int x, y, dx, dy; // Ballkoordinaten von links unten gesehen
    int w, h; // Hoehe und Weite des Applets
    final int s; // Kugelgroesse als Konstante

    public BallCanvas ()
    {
        try
        {
            Back = ImageIO.read(
                    getClass().getClassLoader().getResource("ziegel.png"));
        }
        catch (Exception e)
        {
        }

        w = Back.getWidth(this);
        h = Back.getHeight(this);
        setPreferredSize(new Dimension(w, h));

        x = w / 2;
        s = h / 5;
        y = s;
        dx = 2;
        dy = (int) Math.sqrt(2 * h);

        Ball = createBall(s);

        setDoubleBuffered(true);
    }

    /**
     * Erzeuge eine gerrenderten Ball.
     *
     * @param S Größe
     * @return
     */
    Image createBall (int S)
    {
        int i, j, k;
        int P[] = new int[4 * (S + 1) * (S + 1)]; // fuer die Pixel
        k = 0;
        double red, green, blue, light, x, y, z;

        for (i = -S; i <= S; i++)
            for (j = -S; j <= S; j++)
            { // Berechne x,y,z-Koordinate auf der Balloberflaeche
                x = -(double) i / S;
                y = (double) j / S;
                z = 1 - x * x - y * y;

                if (z <= 0)
                    P[k] = 0;
                // außerhalb des Balls! Transparenter Punkt.
                else
                {
                    z = Math.sqrt(z);
                    light = (x + y + z) / Math.sqrt(3) * 0.4;
                    // Vectorprodukt mit 1,1,1
                    red = 0.6 * (1 + light); // Rotanteil
                    green = 0.2 * (1 + light); // Gruenanteil
                    blue = 0; // Blauanteil
                    P[k] = 255 << 24 | // nicht transparent!
                            (int) (red * 255) << 16 |
                            (int) (green * 255) << 8 |
                            (int) (blue * 255);
                    // P[k] setzt sich in vier Bytes aus den
                    // Farben und der Transparenz zusammen
                }
                k++;
            }
        return createImage( // Erzeuge das Image
                new MemoryImageSource(2 * S + 1, 2 * S + 1,
                        ColorModel.getRGBdefault(),
                        P, 0, 2 * S + 1));
    }

    /**
     * Zeichne Hintergrund. Zeichne den Ball an der richtigen Stelle.
     */
    @Override
    public void paint (Graphics g)
    {
        g.drawImage(Back, 0, 0, this);
        g.drawImage(Ball, x - s, h - y - s, this);
    }

    /**
     * Schleife des Threads. Der Ball hüpft mit Schwerkraft und verliert bei
     * jedem Aufprall an Energie, bis er nur noch rollt.
     */
    @Override
    public void run ()
    {
        while (!stop)
        {
            // Ball verschieben:
            if (x + dx - s <= 0)
                dx = -dx;
            if (x + dx + s >= w)
                dx = -dx;
            if (y + dy - s <= 0)
            {
                dy = -dy - 1;
                y = s;
            }
            else
                dy--;
            x += dx;
            y += dy;

            repaint();
            // Etwas verzögern
            try
            {
                Thread.sleep(10);
            }
            catch (Exception e)
            {
            }
        }
    }

}

public class Test extends JFrame implements FocusListener
{
    BallCanvas ball;
    boolean stop;

    public Test ()
    {
        super("Ball Animation");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setIconImage(new javax.swing.ImageIcon(
                getClass().getResource("hearts32.png")).getImage());

        ball = new BallCanvas();
        add("Center", ball);

        addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing (WindowEvent e)
            {
                ball.stop = true;
            }
        });

        addFocusListener(this);

        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main (String args[])
    {
        new Test();
    }

    // Routinen für FocusListener

    @Override
    public void focusGained (FocusEvent e)
    {
        ball.stop = false;
        new Thread(ball).start();

    }

    @Override
    public void focusLost (FocusEvent e)
    {
        ball.stop = true;
    }

}

Natürlich ist es auch möglich, ein Image in RGB-Werte zu zerlegen.

    static Image turn (Image I, Component c)
    {   
        int W=I.getWidth(c),H=I.getHeight(c);
        int P[]=new int [W*H];
        PixelGrabber pg=new PixelGrabber(I,0,0,W,H,P,0,W);
        try { pg.grabPixels(); } catch (Exception e) { return I; }
        int Q[]=new int[W*H];
        int i,j;
        for (i=0; i<H; i++)
            for (j=0; j<W; j++)
            {   
                Q[i*W+j]=P[i*W+j];                  
            }
        return Toolkit.getDefaultToolkit().createImage(
            new MemoryImageSource(H,W,ColorModel.getRGBdefault(),
                            Q,0,H));
    }

Beispiel - Merge Sort mit Threads

Da moderne Prozessoren immer mehrere Threads haben, liegt es nahe, den Merge Sort aus diesem Beispiel mit Threads zu beschleunigen. Wir teilen dazu den Originalhaufen in zwei Teile auf und sortieren beide in einem separaten Thread, ohne weitere Threads zu erzeugen. Das beschleunigt den Algorithmus auf dem verwendeten aktuellen Rechner von 10 Sekunden auf 6 Sekunden.

Die Implementation is allerdings wesentlich komplexer. Wir benötigen eine Klasse die Runnable implementiert und alle Informationen über die zu sortierenden Teile enthält.

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Hilfsklasse die einen Thread erzeugen kann, der einen Teil sortiert.
 *
 * @param <Type>
 */
class SortRunner<Type extends Comparable<Type>> implements Runnable
{
    ThreadedMergeSorter<Type> sorter;
    Type[] array;

    int start, end;

    /**
     * Wir merken uns alle Parameter, da run() keine Parameter hat
     *
     * @param sorter
     * @param array
     * @param start
     * @param end
     */
    public SortRunner (ThreadedMergeSorter<Type> sorter, Type[] array,
            int start,
            int end)
    {
        this.sorter = sorter;
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    public void run ()
    {
        sorter.sort(array, start, end);
    }
}

/**
 * Sortiert ab 10000 Elementen, indem zwei Hälften mit Threads sortiert werden.
 *
 * @param <Type>
 */
class ThreadedMergeSorter<Type extends Comparable<Type>>
{
    Type[] work;

    @SuppressWarnings("unchecked")
    public void sort (Type[] array)
    {
        if (array.length < 2)
            return;
        work = (Type[]) java.lang.reflect.Array.newInstance(array[0].getClass(),
                array.length);
        sortthreaded(array, 0, array.length - 1);
    }

    void sortthreaded (Type[] array, int start, int end)
    {
        if (end - start < 10000) // Array ist klein
        {
            sort(array, start, end);
        }
        else // Array ist groß
        {
            ThreadPoolExecutor executor = //
                    (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

            int half = end / 2;

            SortRunner<Type> runner1 = new SortRunner<Type>(this, array, 0,
                    half);
            executor.execute(runner1);
            SortRunner<Type> runner2 = new SortRunner<Type>(this, array,
                    half + 1, end);
            executor.execute(runner2);
            executor.shutdown();

            try
            {
                executor.awaitTermination(100, TimeUnit.SECONDS);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            merge(array, 0, half, half + 1, end);
        }
    }

    void sort (Type[] array, int start, int end)
    {
        // System.out.println(start + "-" + end);
        int half = (start + end) / 2;
        if (half - start > 0)
            sort(array, start, half);
        if (end - (half + 1) > 0)
            sort(array, half + 1, end);
        merge(array, start, half, half + 1, end);
    }

    void merge (Type[] array, int start1, int end1, int start2, int end2)
    {
        int aim = start1;
        int start = start1;
        while (true)
        {
            if (array[start1].compareTo(array[start2]) < 0)
            {
                work[aim++] = array[start1++];
                if (start1 > end1)
                {
                    while (start2 <= end2)
                        work[aim++] = array[start2++];
                    break;
                }
            }
            else
            {
                work[aim++] = array[start2++];
                if (start2 > end2)
                {
                    while (start1 <= end1)
                        work[aim++] = array[start1++];
                    break;
                }
            }
        }
        System.arraycopy(work, start, array, start, aim - start);
    }
}

public class Test
{
    public static void main (String args[]) throws Exception
    {
        int N = 10000000;
        String[] array = new String[N];
        for (int i = 0; i < N; i++)
            array[i] = "String " + Math.random();

        ThreadedMergeSorter<String> sorter = new ThreadedMergeSorter<String>();

        long time = System.currentTimeMillis();
        sorter.sort(array);
        System.out.println((System.currentTimeMillis() - time) + " msec");

        for (int i = 0; i < N - 1; i++)
            if (array[i].compareTo(array[i + 1]) > 0)
                throw new Exception("Error in Sort");
    }
}

 

 

Übungsaufgaben

  1. Schreiben Sie das Programm mit der Zeitanzeige so um, dass eine einfache Uhr mit Zeigern angezeigt wird.

Lösungen.

Aufgaben ohne Lösung

  1. Erzeugen Sie ein Runnable-Objekt mit einer int-Variablen n. Lassen Sie zwei Threads diese Variable hochzählen (mit Ausgabe auf dem Schirm), bis sie einen Wert überschreitet. Beobachten Sie, ob die Ausgabe chonologisch richtig ist. Gibt es vielleicht sogar doppelte Ausgaben? Sind solche doppelte Ausgaben denkbar?
  2. Schreiben Sie ein Programm, dass einen Text von links nach rechts laufen läßt. Wählen Sie einen Font mit 2/3 der Fenterhöhe. Die Animation soll ähnlich wie bei den Bällen ablaufen und gepuffert sein.
  3. Lassen Sie mehrere Bälle springen, mit oder ohne Kollisionstest.

Zurück zum Java-Kurs