Threads III – Erzeuger, Verbraucher und ein neuer Zustand

(Fortsetzung von hier.) Im bisherigen Beispiel ging es um Toilettenbenutzer, die auf eine leere Toilette warten, sie dann auf jeden Fall benutzen können und auf jeden Fall wieder herausgehen, so dass die Toilette dann für den nächsten Besuch wieder frei ist. Wir hatten quasi Erzeuger, die den kritischen Bereich betreten, wo auf jeden Fall Platz zum, äh, Erzeugen ist, und dann gehen sie wieder. Das erzeugte Produkt bleibt nicht etwa in der Toilette, sondern verschwindet im Orkus.
Dafür reichen unsere bisherigen synchronisierten Methoden.

Erzeuger und Verbraucher

Wir erweiterten jetzt unsere Aufgabe zu etwas, das allgemein Erzeuger-Verbraucher-Problem heißt. Dazu stellen wir uns einen oder mehrere Erzeuger vor, die ein Produkt erstellen und das an einem Speicherort abstellen wollen, das aber nur können, wenn dort Platz ist. Dazu kommen noch ein oder mehrere Verbraucher, die regelmäßig diesen gemeinsamen Speicherort aufsuchen, um von dort ein Produkt abzuholen – das geht natürlich nur, wenn dort auch ein Produkt für sie liegt.

Damit kommt ein neuer Faktor hinzu. Nicht nur gibt es einen kritischen Bereich, der nur von einem (sagen wir mal) auf einmal betreten werden darf. Außerdem gibt es in diesem Bereich noch eine weitere Bedingung, die erfüllt sein muss: der Lieferant muss dort Platz für sein Produkt haben, und der Abholer muss dort eine Kiste zum Abholen vorfinden.

threads_erz-verb

Lösung 1 (keine gute Lösung)

In einer Schleife produziert der Erzeuger immer wieder ein Produkt (im Beispiel eine Kiste) und ruft danach die synchronisierte Methode hineinlegen(Kiste) des Speicher-Objekts (der ein Monitor ist) auf. Damit hat er ein Exklusivrecht auf die vom Monitor überwachten Methoden. Wenn dort kein Platz zum Ablegen der Kiste ist, dann hilft einfaches Warten (in einer Schleife etwa) auch nicht: Der Verbraucher kann ja nicht hinein, um die Kiste dort herauszuholen, solange der Erzeuger die Monitormethode benutzt.

Ebenso der Verbraucher: Wenn der die Methode herausholen() aufruft, die als Rückgabetyp eine Kiste hat, was soll dann passieren, wenn gar keine Kiste im Speicher ist? Einfaches Warten auf eine Kiste funktioniert ebenso wenig wie oben.

Eine Lösung ist: Nu, wenn keine Kiste da ist, dann gibt die Methode herausholen einfach null zurück statt einer Kiste. Das heißt aber, dass der Verbraucher komplizierter wird: Er kann nicht sichergehen, dass er nach dem Holen eine Kiste hat und muss, wenn er stattdessen null hat, diesen einen Kistenholversuch solange wiederholen, bis er doch eine Kiste hat. Dann kann er regulär weitermachen.
Ebenso der Erzeuger: Wenn der eine Kiste hineinlegen will, gibt es üblicherweise keinen Rückgabetyp (void). Man müsste stattdessen eine Rückgabe machen, boolean oder die Kiste selber, anhand derer der Erzeuger unterscheiden kann, ob der Hineinlegen-Versuch erfolgreich war oder nicht. Wenn nicht, wird der Versuch mit der ursprünglichen Kiste solange wiederholt, bis der Erzeuger sie doch los wird. Dann kann er regulär weitermachen.

Diese Lösung funktioniert. Aber zum einen ist der Code dann etwas umständlich, zum anderen führt das dazu, dass der Erzeuger möglicherweise immer und immer wieder versucht eine Kiste abzuliefern, auch wenn der Verbraucher noch so langsam arbeitet. (Oder umgekehrt.) Jeder Thread läuft ständig in einer Schleife, auch wenn gar nichts passiert. Das kostet Rechenzeit.

Deshalb macht man das anders.

Lösung 2: Ein neuer Zustand

Wenn ein Erzeuger-Thread die synchronisierte hineinlegen-Methode aufruft, dort aber keinen Platz findet, dann wird er ruhen geschickt. Damit gibt er, wichtig, seinen exklusiven Anspruch auf den Monitor vorläufig auf.

Wenn dann mal endlich ein Verbraucher die Kiste abholt und Platz im Speicher-Bereich schafft, muss der Speicher/Monitor danach dem Betriebssystem sagen, dass dies alle von ihm ruhen geschickten Threads aufwecken soll. Auch der ruhende Erzeuger wird so geweckt und versucht dann a) wieder exklusiven Anspruch auf den Monitor zu kriegen (falls das nicht klappt, ist er BLOCKED, siehe vorherigen Eintrag) und im Erfolgsfall b) macht er an genau der Stelle weiter, an der er ruhen geschickt wurde.

Dieser neue Zustand heißt WAITING:

Erzeuger und Verbraucher sehen im einfachsten Fall so aus:

public class Erzeuger extends Thread  {
  // Attribute
  Speicher speicher;
  int produktionsnummer = 0;
 
  //Konstruktor
  public Erzeuger(Speicher speicher) {
    this.speicher = speicher;
  }
 
  //Methoden
  public void run() {
    while (produktionsnummer<20) {
      Kiste k = new Kiste(produktionsnummer);
      produktionsnummer++;
      speicher.hineinlegen(k);
    }
  }
}
public class Verbraucher extends Thread{
    // Attribute
    Speicher speicher;
 
    //Konstruktor
    public Verbraucher(Speicher speicher) {
        this.speicher = speicher;
    }
 
    //Methoden
    public void run() {
        while (true) {
            Kiste k = speicher.herausholen();
        }
    }
}

Klar wird man noch ein sleep() einbauen, damit das nicht gar so schnell geht, und da und dort ein System.out.println(), damit man auch etwas mitkriegt davon, ob gerade eine Kiste produziert oder dem Speicher entnommen wurde, aber das ist eigentlich nur Dekoration.

Schwieriger finde ich die Programmierung des Monitors:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Speicher {
    // Attribute
    boolean istLeer = true;
    Kiste eingelagerteKiste = null;
 
    //Konstruktor
    public Speicher() {
    }
 
    //Methoden
    synchronized void hineinlegen(Kiste neueKiste) {        
        while (!istLeer) { 
            //exklusiver Zugang gelungen, aber fruchtlos
            try {
                wait(); //geh ruhen
            }
            catch (Exception e) { System.out.println(e); }
        }
        //exklusiver Zugang gelungen, und sinnvoll
        istLeer = false;
        eingelagerteKiste = neueKiste;
        notifyAll(); //Wecken der ruhenden Threads
    }
 
    synchronized Kiste herausholen() {
        while (istLeer) {
            //exklusiver Zugang gelungen, aber fruchtlos
            try {
                wait(); //geh ruhen
            }
            catch (Exception e) { System.out.println(e); }
        }
        //exklusiver Zugang gelungen und sinnvoll
        istLeer = true;
        notifyAll(); //Wecken der ruhenden Threads
        return eingelagerteKiste;
    }
}

Mit dem wait() in Zeile 15 und 29 wird der Thread, der diesen Befehl auslöst, in unseren neuen WAITING-Zustand versetzt. Das muss aus Java-Gründen in einem try-catch-Block geschehen. Das notifyAll() in Zeile 22 und 35 sagt dem Betriebssystem, dass wartende Threads jetzt geweckt werden sollen, auf dass sie erneut ihr Glück versuchen.

Erklären muss man jetzt nur die Schleifen um das wait() herum, siehe Zeilen 12 und 26. Warum reicht da nicht ein einfaches if?

Für diesen Fall:
Ein Erzeuger will die exklusive hineinlegen-Methode aufrufen. Der Monitor ist gerade frei, der Erzeuger also nicht BLOCKED, und trifft auf die Schleife, deren Bedingung wahr ist (der Speicher ist voll), so dass der Erzeuger in Winterschlaf versetzt wird (WAITING).
Ein Verbraucher holt dann die Kiste heraus und alle schlafenden Threads werden geweckt. Auch unser erster Thread wird geweckt (ist RUNNABLE), doch ebenso ein zweiter Erzeuger. Der zweite kommt zuerst dran (ist RUNNING), kriegt exklusiven Zugang zum Monitor, legt eine Kiste ein, und verlässt den kritischen Bereich. Jetzt erst kommt unser erster Thread an den Zug (RUNNING), er kriegt seinen exklusiven Zugang zum Monitor – dummerweise ist aber schon wieder kein Platz mehr für die Kiste! Also muss er wieder ruhen und auf das nächste Wecken warten.

Will heißen: Zum Zeitpunkt des Weckens ist die für den Erzeuger kritische Bedingung (dass Platz in der Ablage ist) zwar erfüllt, aber schon kurz danach, wenn der Erzeuger zum Zug kommt, kann sie schon wieder nicht mehr erfüllt sein.


Aufgaben:

  • Legen Sie ein Projekt an mit den Klassen Speicher, Erzeuger, Verbraucher und Starter und testen Sie es.
  • Testen Sie das mit mehreren Erzeugern und Verbrauchern.
  • Ändern Siecdie Klasse Speichervso, dass dort Platz für drei Kisten ist. Sie brauchen dazu ein Feld der Länge 3 und ein Attribut, das angibt, ob Platz für eine neue Kiste ist, und ein zweites, das angibt, ob eine Kiste zum Abholen da ist.

 


Bonus für die, die dabeigeblieben sind

Man kann die Zustände der laufenden Threads schön sichtbar machen. Die Klasse Thread (und damit ihre Unterklassen ebenso) hat in Java eine Methode getState(), die als Rückgabe eine Enum Thread.State hat, nämlich einen der folgenden Werte: NEW, RUNNABLE, TERMINATED, TIMED_WAITING, BLOCKED, WAITING. Nicht dabei ist RUNNING.
Ich habe mir eine Klasse ThreadObserver geschrieben, das sind einige ihre wichtigsten Methoden:

ThreadObserver_kurz

Man kann mit addToWatchlist bestehende Threads zu einer Liste beobachteter Threads hinzufügen. Falls diese Threads noch nicht laufen, kann man sie mit startAllThreads laufen lassen. Threads, die nicht im Zustand NEW sind, werden dabei ignoriert, die laufen oder liefen ja schon. Mit stopEverything werden sowohl alle Threads als auch der Beobachter selber abgebrochen. Wenn man das vergisst, kann es leicht passieren, dass der eine oder andere Thread unbemerkt weiterläuft. Und mit startWriting schaltet man das Protokollieren ein.

Das Protokollieren: Der ThreadObserver fragt regelmäßig alle beobachteten Threads nach ihrem Zustand. Wenn sich der ändert (und um das feststellen zu können, muss der vorhergehende Zustand gespeichert worden sein), dann wird diese Information zur Zeit an ein selbstgeschriebenes ThreadStateWindow und an einen ThreadStatePrinter übergeben; das erste gibt stellt die Änderung in einem Fenster dar, der zweite druckt sie auf die Konsole aus.

Natürlich ist dabei nicht gewährleistet, dass wirklich jede Änderung sofort bemerkt wird. Der ThreadObserver nutzt schließlich selber einen Thread, um die anderen zu beoachten, und wer weiß, wie oft und wann der drankommt. Aber im Prinzip funktioniert das gut.

Wenn man also seine Starterklasse von ThreadObserver erben lässt:

public class Starter extends ThreadObserver {
    // Attribute
    Speicher s;
    Erzeuger e0;
    Verbraucher v0;
 
    //Konstruktor
    public Starter() {
        s = new Speicher();
        e0 = new Erzeuger(s);
        v0 = new Verbraucher(s);
    }
 
    //Methoden
    public void starten() {
        e0.setName("erzeuger0");
        v0.setName("verbraucher0");        
 
        addToWatchlist(e0);
        addToWatchlist(v0);
        startWriting();
 
        e0.start();
        v0.start();        
    }
}

- dann kriegt man die aktuellen Zustände von Erzeuger und Verbraucher angezeigt:

ThreadObserver_window

Ein Problem ist allerdings, dass die Zustände RUNNABLE und BLOCKED so gut wie nie sichtbar werden. Klar: Wenn ein Erzeuger oder Verbraucher mal in den Speicher darf, ist seine Arbeit darin – es sind ja nur ein paar Zeilen Code – so schnell erledigt, dass er a) sofort danach wieder beim Erzeugen oder Verbrauchen ist (TIMED_WAITING) und b) ein eventuell geblockter Thread sofort wieder entblockt wird, laufen darf und dann wieder am Arbeiten ist (TIMED_WAITING).

Eine Lösung ist die Klasse Zeitschinder mit einer (der Einfachheit halber statischen) Methode zeitSchinden. Die wird innerhalb der Speicher/Monitor-Methoden aufgerufen. Darin wird mal eben die Fakultät von 20000 berechnet, so dass die synchronisierten Methoden nicht gar so schnell ablaufen. Dann sieht man auch mehr Zustände:

ThreadObserver_window


Aufgaben:

  • Warum ist immer nur höchstens ein Thread grün? Kann man das ändern?
  • Warum endet das ganze immer damit, dass ein paar Threads grau angezeigt werden und ein paar orange?
  • Was ist der Unterschied zwischen sleep() und wait()? Was der zwischen BLOCKED und WAITING?
  • Bauen Sie den ThreadObserver in eines Ihrer Projekte ein, lassen Sie Ihren Starter davon erben und nutzen Sie dann die Methoden zum Hinzufügen und Protokollieren von Threads. Arbeiten Sie, falls Sie wollen, mit Zeitschinder.zeitSchinden() (Download der beiden Java-Klassen).
  • Erklären Sie mir, wieso bei mir manchmal ein Thread als BLOCKED gemeldet wird, selbst wenn im ganzen Projekt keine einzige synchronisierte Methode (oder synchronisierter Anweisungsblock) verwendet wird. Das verstehe ich selber noch nicht.

 

Damit sind meine drei Blogeinträge zu Threads unter Java beendet, mit denen bin ich auch für den Flipped Classroom vorbereitet: Meine Schülerinnen und Schüler sollen sich das jeweils zu Hause durchlesen und die Aufgaben dazu dann in der Schule lösen. Ich lerne selber ja auch aus aus Texten besser als aus Videos. Natürlich gibt e zu Threads noch mehr zu sagen, und Java bietet noch weitere Möglichkeiten zur Synchronisation an, aber viel mehr weiß ich nicht und für die Schule reicht das eh. Leider ist das Thema Threads im neuen Lehrplan weitgehend gestrichen. Da wird nur noch modelliert, aber nicht mehr implementiert – kein Java mehr, keine echte Umsetzung. Nun, dauert ja noch acht Jahre, bis der in der Oberstufe angelangt ist.

Threads II – Java und ein wenig Nebenläufigkeit

(Fortsetzung von hier.) Stehengeblieben waren wir bei einer Klasse Toilettenbenutzer (eine Unterklasse von Thread), deren Objekte in einer Schleife immer und immer wieder auf die Toilette gehen. Und weil es Threads sind, können die das alle gleichzeitig und unabhängig von einander. Schwierig wird es nur, wenn alle dieselbe Toilette benutzen sollen, und das möglichst nicht gleichzeitig, sondern nacheinander…

Nebenläufigkeit: Die Notwendigkeit für Synchronisation

Wenn alle nur eine Toilette benutzen sollen, dann brauchen wir erst einmal eine solche Toilette:

public class Toilette {   
    public void benutzen(Toilettenbenutzer t) {
        System.out.println(t.getName()+" betritt Toilette.");
        System.out.println(t.getName()+" benutzt Toilette.");
        System.out.println(t.getName()+" spuelt.");
        System.out.println(t.getName()+" waescht Haende.");
        System.out.println(t.getName()+" verlaesst Toilette.");
        System.out.println();
    }    
}

Die Toilette hat eine Methode benutzen, der als Argument ein Toilettenbenutzer übergeben wird – eigentlich nur deshalb, damit wir seinen Namen erfragen und ausgeben könnnen, wer jetzt im Moment gerade die Toilette benutzt. Wir müssen jetzt nur noch unsere Toilettenbenutzer anpassen, damit die auch wirklich die Toilette benutzen. Dazu kriegen sie ein Referenzattribut vom Typ Toilette, das im Konstruktor gleich belegt wird:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Toilettenbenutzer extends Thread {
  Toilette toilette;
  int anzahl = 0;
  public Toilettenbenutzer(Toilette t) {
    toilette = t;
  }
  public void run() {
    while (anzahl < 20) {
      toilette.benutzen(this);  //"this" bezieht sich auf das aktuelle Objekt
      anzahl = anzahl + 1;
      try {
        sleep( (int) Math.random()*1000+500 );
      } catch (Exception e) { System.out.println(e); }
    }
  }
}

(Natürlich müssen wir auch im Starter eine Toilette anlegen und den Konstruktoren übergeben.)

Wenn wir jetzt mit unserem Starter-Objekt eine Reihe von Toilettenbenutzer-Threads anlegen und starten, dann wird das ein heilloses Durcheinander:

Thread-2 betritt Toilette.
Thread-2 benutzt Toilette.
Thread-5 betritt Toilette.
Thread-3 betritt Toilette.
Thread-4 betritt Toilette.
Thread-3 benutzt Toilette.
Thread-5 benutzt Toilette.
Thread-5 spuelt.
Thread-5 waescht Haende.
Thread-5 verlaesst Toilette.
Thread-2 spuelt.

Thread-3 spuelt.
Thread-4 benutzt Toilette.
Thread-3 waescht Haende.
Thread-2 waescht Haende.
Thread-3 verlaesst Toilette.

Wir müssen irgendwie sicherstellen, dass nur einer auf einmal die Toilette benutzen kann. Solche Wünsche gibt es viele: Beim Durchführen von Kontotransaktionen, beim Buchen von Reisen, beim Öffnen und Schreiben auf Dokumenten möchten wir häufig, dass nur einer gleichzeitig heran darf. Der gemeinsam genutzte und zu beobachtende Bereich heißt kritischer Bereich. Wenn es solche Bereiche gibt, wenn sich also parallele Prozesse absprechen müssen, spricht man von Nebenläufigkeit.

Lösungsversuch: Semaphore

Ein Semaphor ist ein Zeichen, das etwa vor einer kritischen Eisenbahnstrecke signalisiert, ob sie gerade befahren werden kann oder nicht. Oder eben ein Besetztzeichen bei einer Toilette. Man sollte meinen, dass man sich so etwas einfach selber programmieren kann:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Toilette {   
    int anzahlBenutzer = 0;
    public void benutzen(Toilettenbenutzer t) {
        while (anzahlBenutzer>0) {
            //t muss halt warten
        }
        anzahlBenutzer = anzahlBenutzer+1;
        System.out.println(t.getName()+" betritt Toilette.");
        System.out.println(t.getName()+" benutzt Toilette.");
        System.out.println(t.getName()+" spuelt.");
        System.out.println(t.getName()+" waescht Haende.");
        System.out.println(t.getName()+" verlaesst Toilette.");
        System.out.println();
        anzahlBenutzer = anzahlBenutzer-1;
    }
}

Neu ist das Attribut anzahlBenutzer, initialisiert mit dem Wert 0. Das ist unser Besetztzeichen: Wenn die Anzahl 0 ist, ist die Toilette frei, wenn die Anzahl 1 ist, ist sie besetzt. (Das könnte man auch mit einem Booleschen Attribut machen, aber so lässt sich das später leichter erweitern, wenn man einen kritischen Bereich hat, der von zwei oder mehre Threads auf einmal betreten werden darf, aber nicht beliebig vielen.)

Wenn ein Thread die benutzen-Methode aufruft, und die Toilette ist besetzt, dann landet er in einer Warteschleife (Zeilen 4–6). Aus der kommt er erst wieder heraus, wenn die Anzahl der Benutzer wieder auf 0 ist. Dann, also ab Zeile 7, wird das Warnsignal um 1 erhöht, das Geschäft verrichtet, und am Ende wird das Warnsignal wieder um 1 vermindert, hat also wieder den Wert 0.

Leider funktioniert das aber nicht. Also, wenn man Glück hat, dann schon. Aber wenn man Pech hat, dann fragt Thread 1 gerade, ob die Toilette frei ist und kriegt ein Ja. Und bevor Thread 1 dann den Semaphor auf besetzt schalten kann, fragt Thread 2 an, ob die Toilette frei ist, und kriegt ebenfalls ein Ja. Schon haben wir den Salat. Dazu kommt noch, dass dann möglicherweise der eine Thread gerade die Anzahl um 1 erhöht und der andere Thread mittenreingrätscht und deshalb eine falsche Anzahl gespeichert wird.

Wenn man zum Beispiel vier der Toilettenbenutzer oben und eine Toilette anlegt und die Toilettenbenutzer startet, dann gibt es folgende Möglichkeiten:

  • alles läuft bestens und niemand kommt sich in die Quere
  • zwei oder mehr Benutzer sind mal gleichzeitig in der Toilette, aber die Anzahl der Toilettenbesucher stimmt (sie ist zwischendurch vielleicht mal auf 2, aber ganz am Ende wieder auf 0)
  • zwei oder mehr Benutzer sind mal gleichzeitig in der Toilette und stören sich beim Erhöhen oder Erniedrigen der Anzahl der Toilettenbesucher. Dann ist die Anzahl zum Beispiel ‑1 (wenn eine der Erhöhungen beim Speichern durch einen anderen Speicherübergang überschrieben wird und deshalb nicht zählt), und das wiederum heißt, dass niemand mehr die Toilette betreten kann und alle weiteren Benutzer für immer in der while-Schleife gefangen sind.

Am besten lässt man zum Ausprobieren jeden Toilettengänger einige hundert Male die Toilette benutzen, und kommentiert die Ausgabezeilen aus, und lässt das sleep() weg, dann geht das schneller. Die benutzen-Methode ist dann so kurz, dass sich zwei Threads leicht in die Quere kommen können:

1
2
3
4
while (anzahlBenutzer>0) { }
  anzahlBenutzer = anzahlBenutzer+1;
  anzahlBenutzer = anzahlBenutzer-1;
}

Wenn die Benutzerzahl 0 am Anfang 0 ist, überspringt Thread 1 die Schleife, dann überspringt Thread 2 die Schleife, dann errechnet Thread 1 den neuen Wert des Semaphoren (nämlich 1), dann errechnet Thread 2 den neuen Wert des Semaphoren (nämlich 2), dann speichert Thread 2 den errechneten Wert (2), dann speichert Thread 1 den errechneten Wert (1), danach reduzieren beide nacheinander den Semaphor, so dass der auf ‑1 steht. Blöd gelaufen.

Das Grundproblem ist nämlich das hier: Bei einem Semaphor muss die Überprüfung seines Zustands (“Ist das Klo frei?”) und die Änderung seines Zustands (“Ich setze das Zeichen auf besetzt.”) gleichzeitig und jedenfalls ununterbrechbar stattfinden. Und das kann man nicht so einfach selber programmieren. Das muss die Programmiersprache dem Programmierer bereits zur Verfügung stellen.

Bei Java geschieht das in Form von Monitoren, die intern mit Semaphoren arbeiten, aber das braucht uns nicht zu interessieren.

Monitore

“Monitor” heißt ursprünglich nicht “Bildschirm”, sondern “Aufseher, Aufpasser” oder in der Schule “Klassenordner”. In Java kann man jedes Objekt zu einem Monitor machen, indem man dem Objekt eine oder mehrere Methoden gibt, denen das Schlüsselwort synchronized vorangestellt ist. Für alle synchronisierten Methoden eines Monitorobjekts gilt: nur eine synchronisierte Methode kann gleichzeitig von irgendwem aufgerufen werden. Wenn eine zweite Methode oder die erste Methode noch einmal aufgerufen wird, wird der aufrufende Thread in einen neuen Zustand versetzt: BLOCKED. Und der bleibt solange in diesem Zustand, bis die zuvor aufgerufene synchronisierte Monitor-Methode beendet wurde und der Monitor dafür sorgt, dass die blockierte Methode wieder RUNNABLE wird. Wir haben also einen neuen Zustand für Threads:

Threads_4

Der Code für unsere Toilette sieht dafür wieder ganz einfach aus, da wir uns nicht selber um Semaphoren kümmern müssen:

public class Toilette {   
    public synchronized void benutzen(Toilettenbenutzer t) {
        System.out.println(t.getName()+" betritt Toilette."+besetzt);
        System.out.println(t.getName()+" benutzt Toilette."+besetzt);
        System.out.println(t.getName()+" spuelt."+besetzt);
        System.out.println(t.getName()+" waescht Haende."+besetzt);
        System.out.println(t.getName()+" verlaesst Toilette."+besetzt);
        System.out.println();
    }
}

Man kann das anzahlBenutzer-Attribut und dessen Änderungen beibehalten, dann lässt sich leicht überprüfen, dass diesmal die Zählerei stets stimmt. Und die Ausgaben sind auch nicht mehr gemischt:

Thread-2 betritt Toilette.
Thread-2 benutzt Toilette.
Thread-2 spuelt.
Thread-2 waescht Haende.
Thread-2 verlaesst Toilette.

Thread-3 betritt Toilette.
Thread-3 benutzt Toilette.
Thread-3 spuelt.
Thread-3 waescht Haende.
Thread-3 verlaesst Toilette.

Thread-2 betritt Toilette.
Thread-2 benutzt Toilette.
Thread-2 spuelt.
Thread-2 waescht Haende.
Thread-2 verlaesst Toilette.

Die Toilettenbenutzer besuchen die Toilette nicht unbedingt abwechselnd (auf die Reihenfolge der Toilettenbenutzer hat man erst einmal keinen Einfluss), aber jedenfalls sicher strikt hintereinander und nie gleichzeitig.

(Fortsetzung folgt. Denn etwas recht Schwieriges und ein weiterer Zustand für Threads kommen noch. Korrekturen bitte gerne als Kommentar.)

Aufgaben:

  • Legen Sie ein Projekt mit den Klassen Starter, Toilette und Toilettenbenutzer an und testen Sie es. Testen Sie es einmal mit einem Monitor (mit synchronisierten Methoden) und einmal ohne das Schlüsselwort synchronized.
  • Legen Sie ein neues Projekt an mit den Klassen Starter, Bankkonto, Einzahler und Abheber an.
    Das Konto erhält ein Attribut int kontostand mit dem Startwert 0 und die (synchronisierten) Methoden einzahlen(int) und auszahlen(int). Diesen Methoden wird jeweils eine positive Ganzzahl übergeben und der Wert des Attributs kontostand entsprechend erhöht oder vermindert.
    Einzahler und Abheber erhalten ein Referenzattribut vom Typ Bankkonto, das wie im Beispiel oben im Konstruktor initialisiert wird. Jeder Einzahler soll zwnazigmal 10 einzahlen, jeder Abheber soll zwanzig 10 abheben.
    Die Starter-Klasse soll für Sie ein Konto, einen Abheber, einen Verbraucher anlegen und starten.
    Am Schluss müsste der Kontostand wieder bei 0 sein. Testen Sie das. Testen Sie das auch mit jeweils zehntausend statt mit zehn Abbuchvorvorgängen.
  • Testen Sie das einige Male ohne das Schlüsselwort synchronized, und schauen Sie, ob der Kontostand am Ende wieder 0 ist. Dazu sollten Sie mit zwanzigtausend statt mit zwanzig Buchungen arbeiten. (Wenn Sie mit sleep() arbeiten, sollten Sie das dazu auskommentieren. Sonst dauert das zu lange.)
  • Testen Sie das alles mit zwei Einzahler- und zwei Abheber-Objekten.
  • Denken Sie sich ein eigenes Beispiel aus, das notwendigerweise mit einem Monitor/synchronisierten Methoden arbeitet, und implementieren Sie es.

Threads I – Allgemeines und erstes Java

Paralleles Arbeiten und die Gründe dafür

Früher, als ich angefangen habe, war das noch einfach: Da hatte ein Computer einen Prozessor: So heißt das Ding, das das eigentliche Rechenarbeiten übernimmt, irgendwo tief drinnen im Computer. Un ein Prozessor heißt, dass eine Rechen- oder sonstige Aufgabe gleichzeitig bearbeitet werden kann. Und doch sah es auch früher so aus, als könnte der Computer zwei Sachen gleichzeitig machen: ein Lied abspielen und gleichzeitig im Textverarbeitungsprogramm die getippten Zeichen einfügen. Vom Laden und Speichern und der Mausbewegung gar nicht zu reden.

Das funktioniert so, dass der Prozessor zwischen den verschiedenen Programmen sehr schnell wechselt: Ein paar Dutzend Millisekunden darf das Musikspielprogramm ran, dann wieder die Textverarbeitung, dann wieder das Musikprogramm, und so weiter. So entsteht der Eindruck, dass diese Prozesse gleichzeitig ablaufen.

Bei meinem aktuellen Windows laufen zur Zeit um die sechzig solcher Prozesse ab. Der Taskmanager gibt einem einen Überblick darüber:

threads_prozesse

Inzwischen haben Computer aber mehr als nur einen Prozessor. Neue kommen mit vier oder acht Prozessoren, und jeder Prozessor kann gleichzeitig und unabhängig von den anderen arbeiten. (Es gibt auch Mischformen, bei denen Prozessoren nur halbwegs unabhängig von einander arbeiten, oder dem Betriebssystem vormachen, es gäbe mehr von ihnen, als eigentlich da sind. Und außerdem hat inzwischen jede Grafikkarte eigene Prozessoren.)

Das ist gut so. Denn richtig viel schneller werden die Prozessoren seit einigen Jahren nicht mehr. Wenn man die Illusion hat, dass Computer trotzdem schneller werden, dann liegt es daran, dass die Prozessorzahl erhöht wird und deshalb mehrere Dinge gleichzeitig erledigt werden können.

Aber selbst wenn man 32 Prozessoren hat, und eine reguläre Rechenaufgabe rechnen will – etwa das Berechnen der Fakultät von 200 000 – dann dauert das noch ziemlich lange, weil sich trotzdem nur ein Prozessor um diese Rechenaufgabe kümmert. Wie viele es sonst noch gibt, ist dann auch egal. (Gut, abgesehen davon, dass generell nicht so viel zwischen Prozessen umgeschaltet werden muss, wenn sie sich auf mehr Prozessoren verteilen können.)

Wenn man möchte, dass es richtig schnell geht mit dem Rechnen, dann muss man die Rechenaufgabe geschickt verteilen auf die zu Verfügung stehenden Prozessoren.

Fakultätsberechnung mit mehreren Prozessoren

Fakultät schreibt man so: n!, und das bedeutet n * (n-1) * (n-2) * (n-3) ... * 1
Als Beispiel: 12! = 12*11*10*9*8*7*6*5*4*3*2*1 = 479 001 600

Es gibt keine Abkürzung oder Formel für das Berechnen der Fakultät: Wenn ich 200000! ausrechnen möchte, muss ich wirklich all diese Multiplikationen durchführen. Das wird schnell sehr aufwendig.

Wenn ich zwei Prozessoren habe, kann ich die Arbeit aber auf beide verteilen. Um 12! zu berechnen, rechnet der eine dann
2*4*6*8*10*12
und der andere rechnet
1*3*5*7*9*11
und am Schluss werden die Ergebnisse multipliziert.

Ich kann das auch auf vier Prozessoren verteilen:
1*5*9
2*6*10
3*7*11
4*8*12

Oder sechs:
1*7
2*8
3*9
4*10
5*11
6*12

Dann geht das Rechnen insgesamt schneller, weil ich alle vorhandenen Prozessoren dazu nutze. Auf meinem Rechner dauert da berechnen von 200000! – eine ziemlich große Zahl – ohne Verteilung auf mehrere Rechenstränge 76 Sekunden, mit vier Rechensträngen 12 1/2 Sekunden, mit acht Strängen gut 10 Sekunden. (Aber natürlich laufen parallel noch viele weitere Prozesse auf meinem Rechner

Prozesse und Threads

Wenn auf einem Rechner mit einem Prozessor vier Programme laufen, dann muss immer wieder zwischen diesen vier Programmen umgeschaltet werden. Das ist aufwendig. Libre Office ist so ein Programm, Firefox ist so ein Programm, iTunes ist so ein Programm. Die laufen im Moment bei mir alle, wenn auch wahrscheinlich auf unterschiedlichen Prozessoren. Das sind sogenannte schwergewichtige Prozesse.

Und dann gibt es sogenannte leichtgewichtige Prozesse, die heißen auch: Threads. Ein Programm (wie Firefox) lässt gleichzeitig viele Threads laufen. Das Umschalten zwischen Threads ist nicht so aufwendig wie das zwischen schwergewichtigen Prozessen: Die gehören ja alle zum gleichen Programm und benutzen den gleichen Arbeitsspeicherbereich für ihre Daten, so dass dieser Bereich dann beibehalten werden kann. (Libre Office und Firefox dagegen nutzen keinen gemeinsamen Arbeitsspeicherbereich, da hat jeder seinen eigenen. Das heißt, dass beim Umschalten jeweils auch der ganze Arbeitsspeicherbereich ausgetauscht werden muss.)

Bei dem Firefox-Prozess läuft zum Beispiel jeder einzelne Tab als Thread. Das hat einen Nachteil: Wenn ein Thread fehlerhaft ist (wenn ein Tab mit einer Seite Probleme hat und hängen bleibt), muss der ganze Prozess abgebrochen werden – der ganze Firefox hängt und muss neugestartet werden. Bei Chrome dagegen läuft jeder Tab als eigener Prozess: Wenn der dann hängt, muss dieser Prozess abgebrochen werden, aber die anderen Prozesse (Tabs) bleiben unbeschädigt.

Zustände eines Threads (für den Anfang)

Threads_1

Wenn ich in einem Programm einen Thread anlege, befindet der sich erstmal im Zustand NEW. Sobald ich ihm dann sage, dass er loslegen soll, befindet er sich im Zustand RUNNABLE: er könnte laufen, wenn er Rechenzeit auf einem Prozessor kriegt. Ob und wann er wirklich zum Rechnen kommt, darauf hat er keinen Einfluss. Das Betriebssystem entscheidet, wer als nächster darf. Dazu greift es nach der einen oder anderen Strategie einen rechenbereiten Thread aus dem Pool aller RUNNABLE Threads heraus und lässt ihn ein wenig rechnen: Dann ist der Thread im Zustand RUNNING. Und wenn das Betriebssystem nach der einen oder anderen Strategie entscheidet, dass jetzt aber wieder genug ist, dann landet der Thread im Zustand RUNNABLE und wartet, bis er wieder mal dran kommt. Wahrscheinlich ist er irgendwann fertig mit seiner Aufgabe, dann ist sein Zustand TERMINATED.

Die Begriffe orientieren sich in diesem Fall an der Sprache Java; in anderen Umgebungen heißen die Zustände vielleicht anders.

Wie ein Thread in Java angelegt wird

Das geht sehr einfach. Java bringt bereits eine Klasse Thread mit, die die wichtigsten Methoden zur Verfügung stellt. Man schreibt sich jetzt einfach selber eine Klasse, die von Thread erbt.

Threads_Toilettenbenutzer

Das einzige, das unsere neue Klasse tun muss ist: die ererbte run-Methode überschreiben. Dann sieht die Klasse zum Beispiel so aus:

public class Toilettenbenutzer extends Thread {
  public void run() {
    System.out.println(getName()+" benutzt die Toilette.");
  }
}

Damit haben wir eine Klasse für jemanden, der einmal aufs Klo geht. Das ist aber langweilig. Also nehmen wir eine Klasse für jemanden, der immer wieder aufs Klo geht:

public class Toilettenbenutzer extends Thread {
  public void run() {
    while (true) {
      System.out.println(getName()+" benutzt die Toilette.");
    }
  }
}

Oder von mir aus auch 20 mal:

public class Toilettenbenutzer extends Thread {
  int anzahl = 0;
  public void run() {
    while (anzahl < 20) {
      System.out.println(getName()+" benutzt die Toilette.");
      anzahl = anzahl + 1;
    }
  }
}

Die Methode getName hat unser Toilettenbenutzer von der Klasse Thread geerbt. Wenn wir den vorgegebenen Namen ändern wollen, können wir das mit setName(String) tun.

Eine letzte Ergänzung: Damit das mit dem Toilettengehen nicht gar so schnell geht, schicken wir unseren Thread jedesmal für zufällige 0.5 bis 1.5 Sekunden schlafen:

1
2
3
4
5
6
7
8
9
10
11
12
public class Toilettenbenutzer extends Thread {
  int anzahl = 0;
  public void run() {
    while (anzahl < 20) {
      System.out.println(getName()+" benutzt die Toilette.");
      anzahl = anzahl + 1;
      try {
        sleep( (int) Math.random()*1000+500 );
      } catch (Exception e) { System.out.println(e); }
    }
  }
}

Neu ist eigentlich nur Zeile 8, aber Java möchte, dass diese ererbte Thread-Methode nur innerhalb eines try-catch-Blockes aufgerufen wird, das macht es etwas unübersichtlicher.

Ein häufiger Fehler ist es, wenn man jetzt die eben liebevoll angelegte run-Methode aufruft. Die wird dann zwar tatsächlich ausgeführt, aber nicht als Thread, sondern regulär, also nicht parallel zu anderen Arbeiten. Vielmehr muss man die start-Methode der Oberklasse Thread aufrufen, und die wiederum ruft die neue run-Methode auf – jetzt aber als Thread.

Jedenfalls haben wir jetzt einen weiteren Zustand von Threads, den des freiwilligen Schlafens:

Threads_3

Zur Wiederholung: Ruft man die Start-Methode auf, wird der Thread in den Zustand RUNNABLE versetzt, und im Zustand RUNNING wird nach und nach die run-Methode ausgeführt (mit Unterbrechungen), bis die run-Methode an ihrem Ende angekommen ist und der Thread im Zustand TERMINATED angekommen ist.

Jetzt kann man beliebig viele Objekte vom Typ Toilettenbenutzer erzeugen und starten, und alle gehen unabhängig voneinander immer wieder auf die Toilette.

Damit wir diese Objekte nicht immer wieder von Hand anlegen müssen, legen wir eine Starter-Klasse an:

public class Starter
{
    Toilettenbenutzer t0;
    Toilettenbenutzer t1;
    Toilettenbenutzer t2;
    Toilettenbenutzer t3;
 
    public Starter() {
        t0 = new Toilettenbenutzer();
        t1 = new Toilettenbenutzer();
        t2 = new Toilettenbenutzer();
        t3 = new Toilettenbenutzer();
    }
    void starten() {
        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

Und wenn man das dann aufruft, kommt zum Beispiel das heraus:

Thread-3 benutzt die Toilette.
Thread-2 benutzt die Toilette.
Thread-1 benutzt die Toilette.
Thread-4 benutzt die Toilette.
Thread-4 benutzt die Toilette.
Thread-3 benutzt die Toilette.
Thread-1 benutzt die Toilette.
Thread-2 benutzt die Toilette.

Schwierig wird es nur, wenn alle dieselbe Toilette benutzen sollen, und das möglichst nicht gleichzeitig, sondern nacheinander…

(Fortsetzung folgt. Und bitte gerne auf fachliche Fehler hinweisen.)

Aufgaben:

  • Legen Sie ein Projekt an, in dem Sie Kopien der Klassen Toilettenbenutzer und Starter anlegen und testen Sie diese.
  • Legen Sie ein anderes Projekt mit zwei Threads an, wobei der eine die Zahlen von 100 bis 1 ausdruckt und der andere die Zahlen von 1 bis 100, und starten Sie sie gleichzeitig.
  • Legen Sie vier Threads an, die jeweils wiederholt eine Zeile Ihrer Wahl ausdrucken, und starten Sie sie gleichzeitig.

Weihnachten I

Jahrelang habe ich Weihnachten dem Sekretariat ein bisschen geholfen, wenn es um Serienbriefe in Sonderformaten ging, insbesondere die jährliche Weihnachtskarte der Schule. Inzwischen gehen die Serienbriefe auch ohne mich, die Weihnachtskarte (Schülerwerke, von den Kunstlehrern ausgewählt) gibt es aber immer noch.

Was ich jahrelang übesehen habe: Natürlich schickt nicht nur meine Schule Weihnachtsgrüße an die Nachbarschulen, die Nachbarschulen schicken natürlich ebensolche an uns!

weihnachtskarten_2015

Die haben wir jetzt im Lehrerzimmer aufgehängt, an einer Schnur, so wie ich es aus England kenne. Da sind welche dabei von Grundschulen und Gymnasien, von externen Partnern der Schule, von Banken. Unterschrieben von der Schulleitung, manchmal auch von Schülersprechern, Schulleitung und Personalvertretern. Ich mag das ja. Einige wenige Kollegen murmeln etwas davon, dass das doch eigentlich unnötig ist, aber so viel Weihnachten muss schon sein!

(Wenn es das bei euch an der Schule nicht gibt: Mal nachfragen, ob die nicht doch irgendwo hängen.)

Ausflug in die Bibliothek

Heute war ich als Begleiter mit einer unserer 6. Klassen in der sehr schönen Stadtbibliothek bei einer kleiner Einführung und Rallyes. Ich habe nicht viele Fotos, weil zu viele Leute darauf sind. Schön verwinkelt ist da alles.

aumuehle1

aumuehle2

Überrascht hat mich (obwohl alles andere als überraschend, wenn man darüber nachdenkt) die zentral platzierte Sammlung von Abitur-Musterlösungen zu allen möglichen Fächern:

aumuehle3

Bosna in Augsburg

Es ergibt sich, dass ich gerade einmal im Jahr an eine Bosna komme: Auf dem Augsburger Christkindlesmarkt. Da treffe ich mich jedes Jahr einmal mit alten Freunden und esse dabei eine Bosna, und zwar hier:

bosna_augsburg

Ich bin aufgewachsen mit Bosna, die waren etwas ganz Normales. Am Judenberg unten, hieß es, gab es die besten. Eine Bosna, das ist: Eine weiße Rinderbratwurst, rohe Zwiebeln, scharfes Paprika- oder Cayennepulver, das in einer langen Semmel. Mit scharf dann Jaguar oder Super-Jaguar. Optional alles mit Käse, aber das ist eine Verirrung.

Als ich dann aus Augsburg fortzog, kam nach und nach die Erkenntnis: Nirgendwo sonst gibt es die Bosna, nirgendwo! AUßer in Wien. Und in Slazburg gibt es etwas, das sie Bosna heißen, aber das ist keine – die tun eine Currymayonnaise oder so etwas dran, und auch sonst ist alles anders. Die Salzburger Bosna, die kann ich auch in München haben, in einem einzigen winzigen Imbiss, aber wer will das schon?

Am Bosnastand oben gibt es die Bosna auch in richtig scharf, als “Mad Dog” auf einer Skala von 1 bis 10, nur ab 18 Jahren. Die letzte Jahre über wurde immer eine Glocke geläutet, wenn ein Kunde diese Spezialität bestellte, am Wochenende fehlte das. (Ein Freund, Stufe 4.) Aber man kriegt jedesmal einen Spruch dazu, mit mäßigem Enthusiasmus vorgetragen: “Nimmst du die Stufe Vier, mir graut vor dir” oder so ähnlich, metrisch etwas akkurater, wenn ich mich richtig erinnere.

Ritter an meiner Schule

Meine Schule ist nach einem historisch zweifelhaften Ritter benannt, der die Reliquien ins Kloster Andechs gebracht haben soll. Ein P‑Seminar hat sich neulich mit der Geschichte unserer Schule beschäftigt, die ist zwar erst gute sechzig Jahre alt, aber das ist ja auch schon alles Geschichte. (Für die Homepage ist aber noch nichts abgefallen, das kommt vielleicht noch.) Im Rahmen dieses Seminars wurde auch, letztlich wohl von einem einzelnen Schüler ausgehend und erstellt, diese Skulptur aufgestellt:

rasso_skultpur

Am Anfang hat mir dieser Ritter gar nicht gefallen. Das ist ja auch gut so, was ist das für eine Kunst, die mir gefällt. Inzwischen habe ich mich aber sehr damit angefreundet. Gefällt mir. Vor allem würde ich gerne sehen, wie sich unsere Schule nach und, alle paar Jahre etwas wachsend, mit solchen Skultpuren füllt, da und dort auf dem Gelände.

Alles über: Studiendirektoren

Aus Gründen war eine kleine Schachtel Pralinen in meinem Fach in der Schule gelandet, und weil ich die nicht alleine essen wollte, habe ich sie an die Theke im Lehrerzimmer zu den dort wartenden Kollegen gebracht. Die haben nicht speziell auf Pralinen gewartet, sondern einfach nur so. “Wie kommt’s?”, fragten die Kollegen. “Ihr wisst doch, dass ich befördert worden bin, da wollte ich was springen lassen.” Gelächter.

Das war ein in der Situation sehr lustiger Witz, finde ich. Also: Ja, ich bin seit einem guten Monat Studiendirektor, StD, oder, um es direkt zu sagen, Besoldungsgruppe A15. (Das wussten die Kollegen.) Und es ist da üblich, etwas mehr zu spendieren als ein bisschen Schokolade. Das kommt auch noch.

A15: Das ist man sonst als verbeamteter Chefarzt oder Fregattenkapitän. Am Gymnasium wird man das, wenn man ein paar Jahre Aufgaben am Kultusministerium oder ISB erledigt, Stellvertreter der Schulleitung ist oder Mitarbeiter im Direktorat, oder wenn man lange genug auf einer anderen Funktionsstelle sitzt. Wie lange man im letzteren Fall auf die Beförderung wartet, hängt von der Wertigkeit der Funktion ab und – noch viel mehr – von der Beurteilung, die man regelmäßig alle vier Jahre erhält. Aus all diesen Daten errechnet sich das sogenannte fiktive Beförderungsdatum. Das liegt meist etliche Jahre in der Zukunft, kann aber auch mal in der Vergangenheit liegen. Alle Leute mit gleichem fiktiven Beförderungsdatum werden gleichzeitig befördert, und zwar zu dem Zeitpunkt, an dem genügend Stellen für die ganze Kohorte freigeworden sind.

Sein fiktives Beförderungsdatum kann man bei der Schulleitung erfahren, der Bayerische Philologenverband bietet seinen Mitgliedern auch eine Excel-Datei zum Herunterladen an, mit denen man es errechnen kann. (Der Hauptpersonalrat macht dieses Angebot nicht, dabei würde ich mir das von denen erwarten.) Da kann man auch mal vergleichen, ob die Daten überall aktuell sind.

Wie gerecht ist dieses System? Ich kann grundsätzlich mit der Tatsache leben, dass es an unterschiedlichen Schularten – Grund‑, Mittel‑, Real‑, Förderschule, Gymnasium, und was es noch gibt – unterschiedliche Besoldungsgruppen gibt. Angebot und Nachfrage und Qualifikationen bestimmen das Gehalt, nicht die Menge an Arbeit. Wenn man bezahlen würde danach, wie viel oder schwer Leute arbeiten, dann wären wir in einem anderen System. Aber dass ich so viel verdiene wie die Schulleitung einer Realschule oder mehr (glaube ich) als die Schulleitung einer Mittelschule – das kann eigentlich nicht sein.

Dass überhaupt manche Lehrkräfte am Gymnasium Studiendirektoren werden und andere nicht, halte ich für schwierig. Man wird es jedenfalls nicht dadurch, dass man eine gute Lehrerin oder ein guter Lehrer ist. Das fände ich besser. Man wird es dann, wenn man einen aus einer begrenzten Menge an Jobs übernimmt, von denen manche viel mehr und andere viel weniger Arbeit machen. Und in einem Lehrerkollegium wird sehr genau darauf geachtet, welche Jobs wann frei werden und wer sich darauf bewerben wird. Wenn man Pech hat, gibt es auf absehbare Zeit keine solchen Posten. Dann muss man sich wegbewerben, wenn man nur wüsste, wohin.
Gerechter fände ich es, solche Jobs auf Zeit zu vergeben, mit finanziellem Bonus, aber ohne Relevanz für eine Beförderung. Aber klar: Schulpolitisch sehe ich es ein, wenn man versucht, am Gymnasium diese Beförderungsstellen (die dem Finanzministerium wohl schon lange ein Dorn im Auge sind) zu erhalten, irgendwie.

Trotzdem freue ich mich natürlich. Jetzt muss ich aber auch mehr arbeiten, damit ich das wirklich verdiene.

Hugo Ball & Dada

Der folgende Text von Hugo Ball ist ein Klassiker aus seinen Tagebucherinnerungen, “Die Flucht aus der Zeit” von 1927. Darin schildert eher die dadaistische Aufführung eines dadaistischen Gedichts. Den Dadaismus behandle ich in Deutsch nur ganz am Rande im Zusammenhang mit dem Expressionismus, aber ein dadaistisches Gedicht müssen ein paar Schülerinnen und Schüler der Oberstufe ihren Mitschülern schon präsentieren.

Ein Foto habe ich nicht gemacht, und an den Aufzug von Hugo Ball – siehe Bericht und Foto unten – sind die Schüler nicht herangekommen. Aber ein mehrstimmiger Vortrag, etwas Verkleidung und eine Schriftrolle waren involviert.

dada_schriftrolle


23. VI 1916

Ich habe eine neue Gattung von Versen erfunden, “Verse ohne Worte” oder Lautgedichte, in denen das Balancement der Vokale nur nach dem Werte der Ansatzreihe erwogen und ausgeteilt wird. Die ersten dieser Verse habe ich heute abend vorgelesen. Ich hatte mir dazu ein eigenes Kostüm konstruiert. Meine Beine standen in einem Säulenrund aus blauglänzendem Karton, der mir schlank bis zur Hüfte reichte, so dass ich bis dahin wie ein Obelisk aussah. Darüber trug ich einen riesigen, aus Pappe geschnittenen Mantelkragen, der innen mit Scharlach und außen mit Gold beklebt, am Halse derart zusammengehalten war, dass ich ihn durch ein Heben und Senken der Ellbogen flügelartig bewegen konnte. Dazu einen zy­linderartigen hohen, weiß und blau gestreiften Schamanen­hut.

Ich hatte an allen drei Seiten des Podiums gegen das Publikum Noten­ständer errichtet und stellte darauf mein mit Rotstift gemaltes Manu­skript, bald am einen, bald am andern Notenständer zelebrierend. Da Tzara von meinen Vorbereitungen wusste, gab es eine richtige kleine Premiere. Alle waren neugierig. Also ließ ich mich, da ich als Säule nicht gehen konnte, in der Verfinsterung auf das Podest tragen und begann langsam und feierlich:

gadji beri bimba
glandridi lauli lonni cadori
gadjama bim beri glassala
glandridi glassala tuffm i zimbrabim
blassa galassasa tuffm i zimbrabim . ..

Die Akzente wurden schwerer, der Ausdruck steigerte sich in der Ver­schärfung der Konsonanten. Ich merkte sehr bald, dass meine Ausdrucks­mittel, wenn ich ernst bleiben wollte (und das wollte ich um je­den Preis) dem Pomp meiner Inszenierung nicht würden gewachsen sein. Im Publi­kum sah ich Brupba­cher, Jelmoli, Laban, Frau Wiegmann. Ich fürchtete eine Blamage und nahm mich zusammen. Ich hatte jetzt rechts am Notenständer “Labadas Gesang an die Wolken” und links die “Elefanten­karawane” ab­solviert und wandte mich wieder zur mittleren Staffelei, fleißig mit den Flügeln schlagend. Die schwe­ren Vokalreihen und der schleppende Rhythmus der Elefanten hatten mir eben noch eine letzte Steige­rung erlaubt. Wie sollte ich’s aber zu Ende führen? Da bemerkte ich, dass meine Stimme, der kein ande­rer Weg mehr blieb, die uralte Kadenz der priesterlichen Lamentation annahm, jenen Stil des Messge­sangs, wie er durch die katholischen Kirchen des Morgen‑ und Abendlan­des wehklagt.

Ich weiß nicht, was mir diese Musik eingab. Aber ich begann meine Vokalreihen rezitativartig im Kir­chenstile zu singen und versuchte es, nicht nur ernst zu bleiben, sondern mir auch den Ernst zu erzwin­gen. Einen Moment lang schien mir, als tauche in meiner kubistischen Maske ein bleiches, verstörtes Jungengesicht auf, jenes halb erschrockene, halb neugierige Gesicht eines zehnjährigen Knaben, der in den Totenmessen und Hochmessen seiner Heimatpfarrei zitternd und gierig am Munde der Priester hängt. Da erlosch, wie ich es bestellt hatte, das elektrische Licht, und ich wurde vom Podium schweiß­bedeckt als ein magischer Bischof in die Versenkung getragen.

24. VI 1916

Vor den Versen hatte ich einige programmatische Worte verlesen. Man verzichte in dieser Art Klangge­dichte in Bausch und Bogen auf die durch den Journalismus verdorbene und unmöglich gewordene Sprache. Man ziehe sich in die innerste Alchimie des Wortes zurück, man gebe auch noch das Wort preis, und bewahre so der Dichtung ihren letzten und heiligsten Bezirk. Man verzichte darauf, aus zweiter Hand zu dichten: nämlich Worte zu übernehmen (von Sätzen ganz zu schweigen), die man nicht funkelnagelneu für den eigenen Gebrauch erfunden habe. Man wolle den poetischen Effekt nicht länger durch Maßnahmen erzielen, die schließlich nichts weiter seien als reflektierte Eingebungen oder Arrangements verstohlen angebotener Geist‑, nein Bildreichigkeiten.

hugo_ball