-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathThreading.html
More file actions
1593 lines (1485 loc) · 109 KB
/
Threading.html
File metadata and controls
1593 lines (1485 loc) · 109 KB
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html><html lang="de"><head>
<title>Variationen zum Thema: Android</title>
<meta name="title" content="Variationen zum Thema: Android">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta charset="UTF-8">
<meta name="description" content="Eine Einführung in mobile Anwendungen">
<meta name="keywords" content="Android,Java,Einführung,Mobile Anwendungen">
<meta name="author" content="Ralph P. Lano">
<meta name="robots" content="index,follow">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="book.css">
</head>
<body><center>
<div id="wrap">
<ul class="sidenav">
<p><a href="index.html">Variationen zum Thema</a><a href="index.html">Android</a></p>
<li><a href="Intro.html">Intro</a></li>
<li><a href="UI.html">UI</a></li>
<li><a href="Graphics.html">Graphics</a></li>
<li><a href="Persistence.html">Persistence</a></li>
<li><a href="Sensors.html">Sensors</a></li>
<li><a href="Threading.html">Concurrency</a></li>
<li><a href="Networking.html">Networking</a></li>
<li><a href="Multimedia.html">Multimedia</a></li>
<li><a href="Performance.html">Performance</a></li>
<li><a href="Library.html">Library</a></li>
<li><a href="Services.html">Services</a></li>
<li><a href="Cryptography.html">Cryptography</a></li>
<li><a href="Addenda.html">Addenda</a></li>
</ul>
<div class="content"><p>
<img src="images/Ch4_Philosphers.png" style="display: block; margin-left: auto; margin-right: auto;width: 333px; height: 180px;" /></p>
<h1>
Concurrency</h1>
<p>
Auf unseren Computern und Telefonen sieht es so aus, als ob viele Dinge gleichzeitig passieren, mehrere geöffnete Fenster oder Aktivitäten, im Hintergrund laufende Dienste, gleichzeitige Downloads, etc. Es ist für uns so natürlich, dass wir es nicht einmal bemerken. Moderne Prozessoren haben mehrere Kerne, 4 bis 8 ist heutzutage die Norm, und die Zahl steigt mit jeder neuen Prozessorgeneration. Noch extremer ist das bei Grafikkarten bei denen die GPUs sogar mehrere tausend "Kerne", sogenannte Shader, haben. Die Frage die sich für uns stellt ist, wie nutzen wir diese zusätzlichen Kerne?</p>
<p>
In unserer ganz normalen Java Anwendung laufen bereits mehrere Threads ohne unser Zutun: In einem einfachen Java-Konsolenprogramm gibt es mindestens den Haupt-Thread und den Garbage-Collector-Thread. Für eine typische Java-UI-Anwendung mit Swing haben wir zusätzlich den UI-Thread und einen Thread zur Verwaltung der Events. Diese "Standard Threads" merken wir normalerweise nicht einmal (es sei denn, sie tun nicht, was sie sollen).</p>
<p>
In diesem Kapitel geht es darum zu lernen, wie wir unsere eigenen Threads schreiben können und wie man allgemeine Fallstricke vermeidet. Die Multi-Thread-Programmierung ist nicht ganz trivial wie wir gleich sehen werden.</p>
<p>
.</p>
<h2>
Timer and TimerTask</h2>
<p>
Der einfachste Weg, um ein paar einfache Aufgaben im Hintergrund zu erledigen, ist mit Androids Timer und TimerTask Klassen. Grundsätzlich sagt der TimerTask, was zu tun ist und der Timer sagt, wann es zu tun ist. D.h. wir verwenden den Timer, um einen TimerTask zu starten. Der folgende Code schreibt alle zwei Sekunden eine kleine Meldung in die Logdatei:</p>
<pre style="margin-left: 40px;">
public class TimerActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
long delay = 3000; // delay in ms before task is executed
long period = 2000; // time in ms between successive executions
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
Log.i("TimerActivity", "run()");
}
}, delay, period);
pause(20000); // wait 20 secs
timer.cancel();
}
}</pre>
<p>
Es ist interessant festzustellen, dass der Task weiterläuft, auch wenn wir die TimerActivity in den Hintergrund schicken. Erst beim Beenden der Application wird der Task beendet. Im LogCat sehen wir dass der Task koninuierlich weiterläuft:</p>
<p>
<img alt="" src="images/TimerTaskActivity.png" style="margin-left: 10px; margin-right: 10px; width: 451px; height: 130px;" /></p>
<p>
.</p>
<p>
Wenn eine Anwendung läuft, verbraucht sie Strom. Das gilt auch für die TimerActivity, selbst wenn sie im Hintergrund läuft. Wir sollten daher in der onPause() Methode dafür sorgen, dass evtl. laufende Threads angehalten werden, um die Batterie unseres Nutzers zu schonen. Für den Timer macht das die <em>cancel()</em> Methode.</p>
<p>
.</p>
<h2>
<img alt="" src="images/ProgressBarActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />AsyncTask</h2>
<p>
Der TimerTask ist einfach zu handhaben und tut was er soll. Allerdings, darf man aus der run() Methode des TimerTasks nicht auf UI Elemente zugreifen. Warum, sei dahin gestellt, wichtig ist, dass der große Bruder des TimerTasks, der AsyncTask das darf.</p>
<p>
Ein klein wenig komplizierter ist der AsyncTask schon. Aber wir haben eben den Vorteil, dass wir auch auf die Benutzeroberfläche zugreifen können. Eine typische Anwendung ist z.B. ein Fortschrittsbalken. Wir beginnen mit einem sehr einfachen Beispiel, das lediglich aus einem ProgressDialog besteht:</p>
<pre style="margin-left: 40px;">
public class ProgressBarActivity extends Activity {
private ProgressDialog progressDialog;
private Context context;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.context = this;
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(true);
progressDialog.setMessage("Downloading file...");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setProgress(0);
progressDialog.setMax(100);
progressDialog.show();
<span style="color:#0000ff;">new DownloadTask().execute("I am the parameter");</span>
}
...
}</pre>
<p>
Um den AsyncTask zu starten, rufen wir einfach seine <em>execute()</em> Methode auf, mit oder ohne Parametern. In unserem Beispiel simulieren wir einen Download, der nur etwas Zeit verschwendet:</p>
<pre style="margin-left: 40px;">
private class DownloadTask extends <span style="color:#0000ff;">AsyncTask<String, Integer, String></span> {
@Override
protected void onPreExecute() {
Log.i("DownloadTask.onPreExecute()", "starting...");
}
@Override
protected String <span style="color:#0000ff;">doInBackground</span>(String... params) {
String response = params[0] + " - I am the return value.";
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
<span style="color:#0000ff;">publishProgress(i * 10);</span>
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
@Override
protected void <span style="color:#0000ff;">onProgressUpdate</span>(Integer... values) {
Log.i("DownloadTask.onProgressUpdate()", "percent=" + values[0]);
<span style="color:#0000ff;">progressDialog.setProgress</span>(values[0]);
}
@Override
protected void onPostExecute(String result) {
Log.i("DownloadTask.onPostExecute()", "done: result=" + result);
<span style="color:#0000ff;">progressDialog.dismiss();
Toast.makeText(context, result, Toast.LENGTH_SHORT).show();</span>
}
}
</pre>
<p>
Der Syntax von AsyncTask ist etwas gewöhnungsbedürftig. Zunächst fällt die spitze Klammer mit den Datenypen String, Integer und String auf: diese geben an welche Datentypen als Parameter und Rückgabewerte in den Methoden doInBackground(), onProgressUpdate() und onPostExecute() verwendet werden.</p>
<p>
Der erste, in diesem Fall ein String, bezieht sich auf den Typ des Parametertyps der Methode doInBackground(). Dies ist auch der gleiche der an die execute()-Methode übergeben wird, die von der Aktivity aus aufgerufen wird. Der zweite, in diesem Fall ein Integer, ist der Parametertyp der Methode onProgressUpdate(). Und die letzte, in diesem Fall ein String, ist der Parametertyp der Methode onPostExecute(). Das klingt komplizierter als es ist.</p>
<p>
Wenn wir also die execute() Methode in der Aktivity aufrufen, wird ein neuer Task gestartet und dann dessen doInBackground() Methode aufgerufen. Hier läuft der Task als eigener Thread. Solange wir die doInBackground() Methode nicht verlassen, lebt der Task. Verlässt der Task aber die doInBackground() Methode, dann wird automatisch die onPostExecute() Methode aufgerufen, und das war's.</p>
<p>
Aber der Sinn der Übung war ja ursrpünglich, dass wir den Fortschrittsbalken im UI-Thread verändern können: das geschieht mit der Methode <em>publishProgress()</em>. Die rufen wir in der doInBackground() Methode. Die publishProgress() Methode ruft indirekt die onProgressUpdate() auf, und die wiederum darf auf den UI-Thread zugreifen.</p>
<p>
Also im AsyncTask dürfen sowohl die onProgressUpdate() als auch die onPostExecute() auf den UI-Thread zugreifen. Allerdings die doInBackground() darf das nicht, dort passiert aber in der Regel die ganze Arbeit.</p>
<p>
.</p>
<h2>
Threads</h2>
<p>
Wenn weder TimerTask noch AsyncTask unser Problem lösen, müssen wir zu schwereren Waffen greifen: den Threads. Im Prinzip verwenden sowohl der TimerTask als auch der AsyncTask Threads, nur wir sehen das nicht.</p>
<p>
Es gibt zwei Möglichkeiten aus einer beliebigen Klasse einen Thread zu machen. Die erste ist einfach von der <em>Thread</em> Klasse zu vererben:</p>
<pre style="margin-left: 40px;">
public class MyFirstThread extends Thread {
public void run() {
...
}
}</pre>
<p>
Die zweite ist das <em>Runnable</em> Interface zu implementieren:</p>
<pre style="margin-left: 40px;">
public class MySecondThread implements Runnable {
public void run() {
...
}
}</pre>
<p>
In beiden Fällen müssen wir die <em>run()</em> Methode überschreiben, und ja das ist die gleiche <em>run()</em> Methode wie wir sie seit Karel kennen. Solange wir uns innerhalb der run() Methode befinden, lebt unser Thread. Sobald wir die run() Methode verlassen, ist er tot.</p>
<p>
Wie der Mensch durchläuft das Leben eines Threads drei Phasen, er wird geboren, er lebt und er stirbt irgendwann. Und ähnlich wie bei Menschen, wenn man einmal tot ist, war's das. Soll heißen, man kann Threads nicht wiederbeleben.</p>
<p>
Was wir allerdings noch nicht geklärt haben: wir werden denn Threads geboren? Das macht die start() Methode:</p>
<pre style="margin-left: 40px;">
MyFirstThread th1 = new MyFirstThread();
th1.start();
MySecondThread mst = new MySecondThread();
Thread th2 = new Thread(mst);
th2.start();
</pre>
<p>
Im ersten Fall, wo wir vererben, können wir einfach die start() Methode direkt aufrufen. Im zweiten Fall, übergeben wir das Runnable Objekt an ein Thread Objekt, und starten dieses dann.</p>
<p>
Der Unterschied zwischen dem Aufruf der start()-Methode und dem Aufruf der run()-Methode kann mit einem Sequenzdiagramm schön visualisiert werden.</p>
<p>
<img src="images/Ch4_Threads.png" style="width: 599px; height: 356px;" /></p>
<p>
.</p>
<p>
Zu Lebzeiten können sich Threads in verschiedenen Zuständen befinden:</p>
<ul>
<li>
Running</li>
<li>
Waiting, Sleeping, Blocked</li>
<li>
Ready to run</li>
</ul>
<p>
Der "Thread Scheduler" ist für die Verwaltung der Threads zuständig. Er sorgt dafür, dass jeder Thread mal zum Laufen kommt, und managt auch die Zustände der Threads.</p>
<p>
.</p>
<h2>
<img alt="" src="images/SliderNoThreadingActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />SliderNoThreadingActivity</h2>
<p>
Sehen wir uns mal ein einfaches Beispiel an. Dazu betrachten wir erst einmal die Klasse <em>Slider</em>, bei der es sich einfach um ein GRect handelt:</p>
<pre style="margin-left: 40px;">
private class Slider extends GRect {
public Slider(int size, int color) {
super(size / 2, size);
setFilled(true);
setFillColor(color);
}
}</pre>
<p>
Als nächstes erstellen wir zehn von diesen Sliders und lassen sie einfach von links nach rechts über den Bildschirm gleiten. Bisher haben wir das immer mit unserem <em>game loop</em> gemacht:</p>
<pre style="margin-left: 40px;">
public class SliderNoThreadingActivity extends GraphicsProgram {
...
public void run() {
Slider[] sliders = createSliders();
// game loop
while (true) {
for (int i = 0; i < sliders.length; i++) {
sliders[i].move(STEP, 0);
}
pause(DELAY);
}
}
...
}
</pre>
<p>
Wir haben also eine riesige Schleife, in der wir jedem der Slider sagen, dass er sich bewegen soll. Dann warten wir ein wenig und wiederholen das Ganze. Das ist das einfache Leben, wir haben nur einen Thread.</p>
<p>
.</p>
<h2>
<img alt="" src="images/SliderThreadingActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />SliderThreadingActivity</h2>
<p>
Im obigen Beispiel sind die Sliders ziemlich dumm. Man muss ihnen sagen was sie tun sollen, nämlich sich zu bewegen. Wäre es nicht cool, wenn die Slider so klug wären, sich selbst zu bewegen? Um unsere Slider schlau zu machen, müssen wir einfach nur das <em>Runnable</em> Interface implementieren:</p>
<pre style="margin-left: 40px;">
private class Slider extends GRect <span style="color:#0000ff;">implements Runnable</span> {
public Slider(int size, int color) {
super(size / 2, size);
setFilled(true);
setFillColor(color);
}
public void<span style="color:#0000ff;"> run()</span> {
while (true) {
pause(DELAY);
move(STEP, 0);
}
}
}</pre>
<p>
Sobald wir sagen, dass unsere Klasse das Runnable Interface implementiert, müssen wir auch die <em>run()</em> Methode überschreiben, die zu diesem Interface gehört. In der run() Methode, haben wir eine Schleife, die unserem game loop sehr ähnlich sieht. Jeder Schieberegler macht jetzt seine eigene Bewegung und wartet, ganz von selbst. </p>
<p>
Aber, wie machen wir die Sliders lebendig? Hier kommt die Thread Klasse ins Spiel:</p>
<pre style="margin-left: 40px;">
public class SliderThreadingActivity extends GraphicsProgram {
private final int SLIDER_SIZE = 80;
private final int DELAY = 40;
private final int STEP = 5;
private RandomGenerator rgen = new RandomGenerator();
public void mousePressed(int x, int y) {
// create a new slider
Slider slider = new Slider(SLIDER_SIZE, rgen.nextColor());
add(slider, 0, rgen.nextDouble(0, getHeight()));
// run the slider in a new Thread
<span style="color:#0000ff;">Thread sliderThread = new Thread(slider);
sliderThread.start();</span>
}
...
}
</pre>
<p>
Wir übergeben eines der Runnable Objekte (also einen Slider) an den Konstruktor der Thread Klasse und rufen dann einfach dessen start() Methode auf, um den Thread zu starten. Danach wird der Thread lebendig und macht sein eigenes Ding. Ist ziemlich einfach.</p>
<p>
.</p>
<h2>
Sequence Diagram</h2>
<p>
Um den feinen Unterschied zwischen den beiden Ansätzen oben zu sehen, ist ein Sequenzdiagramm sehr hilfreich. Im Sequenzdiagramm werden die Objekte nach rechts und die Zeit nach unten angetragen. Dabei sieht man wie die verschiedenen Objekte untereinander kommunizieren. Im ersten Beispiel sehen wir einen dicken Balken der den momentan laufenden Thread anzeigt, den einzigen Thread:</p>
<p>
<img alt="" src="images/Ch4_Slider1.png" style="margin-left: 10px; margin-right: 10px; width: 315px; height: 360px;" /></p>
<p>
.</p>
<p>
Im zweiten Beispiel dagegen gibt es mehrere dicke Balken, jedes Mal, wenn ein neuer Thread erstellt wird, fügen wir einen neuen hinzu:</p>
<p>
<img alt="" src="images/Ch4_Slider2.png" style="margin-left: 10px; margin-right: 10px; width: 339px; height: 363px;" /></p>
<p>
.</p>
<p>
Jeder dieser neuen Threads macht sein eigenes Ding. Wir sehen auch, dass es außer beim Erstellen sehr wenig Interaktion zwischen den verschiedenen Threads gibt, und das ist eine gute Sache.</p>
<p>
Für moderne CPUs mit mehreren Kernen bedeutet die Verwendung mehrerer Threads, dass jeder Thread auf einem eigenen Kern laufen kann. Während wir im Single-Thread-Modus programmieren, verwenden wir nur einen Kern, die anderen Kerne tun nichts. Das ist Verschwendung. Wenn wir also unsere Programme beschleunigen wollen, sollten wir Threads verwenden.</p>
<p>
.</p>
<h2>
<img alt="" src="images/Ch4_Philosphers.png" style="margin-left: 10px; margin-right: 10px; width: 333px; height: 180px; float: right;" />Dining Philosophers</h2>
<p>
Also, warum hat jeder so viel Angst vor Threads, scheint bisher ziemlich harmlos zu sein. Nun, es gibt ein sehr berühmtes Beispiel, das sehr schön visualisiert, was schief gehen kann: es nennt sich das <em>Philosophenproblem</em>, oder auf Englisch "Dining Philosophers" [1].</p>
<p>
Die Geschichte geht so: Es waren einmal zwei chinesische Philosophen, die an einem Tisch saßen. Sie philosophierten und die einzige Ablenkung, die sie hatten, war ab und zu zu essen. Jeder Philosoph bekam eine Schüssel Reis, aber es wurden insgesamt nur zwei Stäbchen zur Verfügung gestellt. Jetzt ist es unmöglich Reis mit nur einem Stäbchen zu essen (zumindest für unsere Philosophen). Um zu überleben, muss ein Philosoph also beide Stäbchen bekommen.</p>
<p>
Grundsätzlich gibt es da drei Möglichkeiten:</p>
<ul>
<li>
Der erste Philosoph nimmt sehr schnell beide Stäbchen, isst den Reis und behält die Stäbchen für sich. Die Folge ist, dass es ihm gut geht, aber sein Mitphilosph wird verhungern.</li>
<li>
Der erste Philosoph nimmt sehr schnell beide Stäbchen, isst den Reis und legt die Stäbchen wieder auf den Tisch. Sein Mitphilosph nimmt die Stäbchen, isst. Beiden geht es gut und sie können fleisig weiter philosophieren.</li>
<li>
Jeder nimmt einen Stäbchen und behält es. Dann werden beide verhungern.</li>
</ul>
<p>
Es ist klar, dass das Problem in der Verteilung der Stäbchen liegt. Wenn jeder Philosoph sein eigenes Paar Essstäbchen hätte, würden beide glücklich bis ans Ende ihrer Tage leben. Aber weil es eine gemeinsame Resource gibt, die Stäbchen, und diese Resource für das Überleben unerlässlich ist, könnte es zu Problemen kommen.</p>
<p>
Diese Probleme haben Namen und die häufigsten sind die folgenden vier:</p>
<ul>
<li>
<strong>Race Condition:</strong> Ein Thread hat die kritische Resource und gibt sie nicht zurück. Philosoph 1 hat beide Stäbchen, isst weiter und gibt die Stäbchen nicht zurück.</li>
<li>
<strong>Starvation:</strong> Ein Thread kann nicht laufen, weil er auf eine kritische Resource wartet. Philosoph 2 wartet auf die Stäbchen und verhungert.</li>
<li>
<strong>Dead Lock:</strong> Ein Thread hat einen Teil einer kritischen Ressource, braucht aber einen anderen Teil, um mit seiner Aufgabe fortzufahren. Philosoph 1 hat ein Stäbchen und Philosoph 2 hat das andere. Aber man braucht beide Stäbchen, um zu essen (und zu überleben).</li>
<li>
<strong>Lock Starvation:</strong> Das apssiert, wenn eine kritische Ressource von einem Thread gesperrt ist und somit kein anderer Thread darauf zugreifen kann, was bedeutet, dass der andere Thread verhungert. Wenn einer der Philosophen ein Stäbchen behält und es nicht zurückgibt, dann ist das so etwas.</li>
</ul>
<p>
Im Allgemeinen führt Thread-unsicherer Code zu diesen Problemen. Also müssen wir lernen, wie man Thread-Save-Code schreibt!</p>
<p>
Was ist also die Lösung in unserem Philosophenproblem? Die Lösung besteht aus zwei Teilen: Erstens, wenn ein Philosoph beide Stäbchen bekommt, sollte er sie nach dem Essen zurückgeben und den anderen im Idealfall benachrichtigen. Zweitens, falls er nur eines der Stäbchen bekommt, aber nicht das andere, sollte er dieses zurückgeben, ein wenig warten, und es erneut versuchen.</p>
<p>
.</p>
<h2>
PhilosopherProgram</h2>
<p>
Sehen wir uns das Dining Philosopher Problem mal aus der Nähe an: dazu implementieren wir das Problem in als normale Java Konsolenanwendung. Wir verwenden Standard Java und nicht Android, da wir uns auf das wesentliche konzentrieren wollen. Wir fangen mit unseren Stäbchen an: Das ist eine sehr einfache Klasse, sie hat nur einen Namen als Instanzvariable:</p>
<pre style="margin-left: 40px;">
private class ChopStick {
private String name;
public ChopStick(String name) {
this.name = name;
}
public String getName() {
return name;
}
}</pre>
<p>
Auch das Hauptprogramm ist ziemlich einfach. Es ist ein Standard-Java-Programm. Wir erstellen zwei Essstäbchen, fügen sie zu einer Liste hinzu, und wir erstellen zwei Philosophen mit ihren jeweiligen Threads:</p>
<pre style="margin-left: 40px;">
public class PhilosopherProgram {
private <span style="color:#0000ff;">List<ChopStick> chopSticks;</span>
public PhilosopherProgram() {
// create list and add two chop sticks:
chopSticks<span style="color:#0000ff;"> = Collections.synchronizedList(new ArrayList<ChopStick>());</span>
chopSticks.add(new ChopStick("A"));
chopSticks.add(new ChopStick("B"));
// create two philosophers:
Philosopher p1 = new Philosopher("1", chopSticks);
Philosopher p2 = new Philosopher("2", chopSticks);
Thread t1 = new Thread(p1);
Thread t2 = new Thread(p2);
t1.start();
t2.start();
}
public static void main(String[] args) {
PhilosopherProgram pp = new PhilosopherProgram();
}
...
}
</pre>
<p>
Wichtig ist, dass unsere Liste von zwei Chopsticks, <em>chopSticks</em>, die gemeinsame Resource ist. Deshalb haben wir die Liste mit <em>synchronizedList()</em> zu einer synchronisierten Liste gemacht. Wann immer wir eine Liste zwischen Threads teilen, müssen wir sicherstellen, dass es sich um eine synchronisierte Version handelt.</p>
<p>
Die <em>Philosopher</em> Klasse selbst implementiert das Runnable Interface, d.h. sie ist ein Thread. Zusätzlich hat sie einen Namen, eine Referenz auf die gemeinsame Ressource, die <em>chopSticks</em>, und sie hat einen Lebenszähler, die <em>liveForce</em>. Ist die <em>liveForce</em> auf Null, ist der Philosoph tot.</p>
<pre style="margin-left: 40px;">
private class Philosopher implements Runnable {
private String name;
private List<ChopStick> chopSticks;
private int liveForce = 5;
public Philosopher(String name, List<ChopStick> chopSticks) {
this.name = name;
this.chopSticks = chopSticks;
}
@Override
public void run() {
...
}
}
</pre>
<p>
Was uns im weiteren interessiert ist zu sehen, was alles in der run() Methode schief gehen kann.</p>
<h3>
Philosopher v.1</h3>
<p>
In unserem ersten Versuch schreiben wir Code, wie wir es normalerweise tun würden. Also, solange wir noch liveForce haben, laufen wir weiter. Bei jeder Iteration verlieren wir ein Leben. Nur wenn wir beide Stäbchen haben, können wir essen und damit unsere liveForce wieder auffüllen. Wenn wir kein Leben mehr haben, sind wir tot.</p>
<pre style="margin-left: 40px;">
private void run() {
ChopStick cs1 = null;
ChopStick cs2 = null;
while (liveForce > 0) {
liveForce--;
System.out.println("Philosopher #" + name + " still has "
+ liveForce + " lives.");
// wait between 0 and DELAY milliseconds
<span style="color:#0000ff;">pause(new Random().nextInt(DELAY));</span>
// try to get both chop sticks
if (cs1 == null) {
if (chopSticks.size() > 0) {
cs1 = chopSticks.remove(0);
System.out.println("Philosopher #" + name
+ " got chop stick " + cs1.getName());
}
} else {
if (chopSticks.size() > 0) {
cs2 = chopSticks.remove(0);
System.out.println("Philosopher #" + name
+ " got chop stick " + cs2.getName());
}
}
// do we have both chop sticks?
if ((cs1 != null) && (cs2 != null)) {
liveForce = 5;
}
}
System.out.println("Philosopher #" + name + " is dead.");
}
</pre>
<p>
Was die Stäbchen angeht, versuchen wir zuerst, das ersten Stäbchen zu bekommen. Sobald wir das erste haben, versuchen wir das zweite zu bekommen.</p>
<p>
Das mag jetzt etwas komisch erscheinen, aber es ist meist nicht ganz trivial Threading Probleme zu entdecken. Deswegen übertreiben wir hier ein wenig, damit die Problem deutlich zu Tage treten. Deshalb haben wir die Pause oben eingefügt. Die Probleme existieren trotz Pause, sie treten dann aber nur seltener auf. (Für manch einen mag das auch schon eine Lösung sein...)<br />
<br />
Wenn wir diesen Code ein paar Mal ausführen, bemerken wir zwei verschiedene Ergebnisse. Die meiste Zeit werden beide Philosophen sterben, denn jeder bekommt einen Stäbchen, das ist ein <em>dead lock</em>, das zum Verhungern (<em>starvation</em>) führt. Aber ab und zu bekommt der eine Philosoph beide Stäbchen (<em>race condition</em>) und der andere Philosoph wird sterben (<em>starvation</em>). Aber es passiert nie, dass beide Philosophen sehr lange überleben.</p>
<p>
Man sieht das beispielhaft an der folgende Ausgabe im LogCat:</p>
<table border="1" cellpadding="1" cellspacing="1" style="width: 500px;">
<tbody>
<tr>
<td>
<p style="text-align: center;">
<img alt="" src="images/Philosopher1a.png" style="margin-left: 10px; margin-right: 10px; width: 350px; height: 370px;" /><br />
Philosopher1a.png</p>
</td>
<td>
<p style="text-align: center;">
<img alt="" src="images/Philosopher1b.png" style="margin-left: 10px; margin-right: 10px; width: 350px; height: 370px;" /><br />
Philosopher1b.png</p>
</td>
</tr>
</tbody>
</table>
<p>
. </p>
<h3>
Philosopher v.2</h3>
<p>
Offensichtlich haben wir zwei Probleme. wir wollen das zweite zuerst lösen: natürlich, wenn wir anständige Philosophen sind, dann müssen wir die Stäbchen nach dem Essen zurückgeben.</p>
<pre style="margin-left: 40px;">
private void run() {
...
// do we have both chop sticks?
if ((cs1 != null) && (cs2 != null)) {
liveForce = 5;
<span style="color:#0000ff;"> // return chop sticks
chopSticks.add(cs1);
chopSticks.add(cs2);
cs1 = null;
cs2 = null;
System.out.println("Returned chop sticks.");</span>
}
...
}
</pre>
<p>
Wenn wir diesen Code ein paar Mal ausführen, stellen wir fest, dass jedes Mal beide Philosophen sterben, nur dauert es etwas länger. Wir haben das Problem der <em>race condition</em> gelöst. Das ist also eine gute Sache (für uns, nicht für die Philosophen).</p>
<p>
Im LogCat sieht das so aus:</p>
<table border="1" cellpadding="1" cellspacing="1" style="width: 500px;">
<tbody>
<tr>
<td>
<p style="text-align: center;">
<img alt="" src="images/Philosopher2.png" style="margin-left: 10px; margin-right: 10px; width: 356px; height: 373px;" /><br />
Philosopher2.png</p>
</td>
</tr>
</tbody>
</table>
<p>
.</p>
<h2>
Synchronization</h2>
<p>
Wie lösen wir das Deadlock Problem? Das heißt in unserem Beispiel, dass jeder Philosoph nur ein Stäbchen hat? Eine Idee wäre, nachdem wir den ersten Chop-Stick haben, zu versuchen den zweiten zu bekommen. Wenn wir es schaffen, gut. Wenn wir es nicht schaffen, könnten wir unseren ersten Chop-Stick zurücklegen und fangen von vorne an. Das sieht zunächst nach einer guten Lösung aus. Es ist auch besser als die anderen Lösungen, die wir oben versucht haben. Aber wenn wir den Code ausführen, merken wir immer noch, dass die Philosophen weiter sterben. </p>
<p>
Wir könnten noch ein paar andere Sachen versuchen, aber es verschiebt nur das eigentliche Problem. Wir brauchen eine radikal andere Lösung: Was, wenn wir dem anderen Philosophen verbieten könnten, an die Stäbchen zu kommen? Wir bauen eine Art Barriere auf dem Tisch, die es uns erlaubt, beide Stäbchen zu greifen. Dann essen wir, und nachdem wir fertig sind, geben wir die Stäbchen zurück und entfernen die Barriere. Dann kann der andere das Gleiche tun. Dieses "Barrierebauen" ist das, was man <em>Synchronisation</em> nennt.</p>
<h3>
Philosopher v.3</h3>
<p>
Der Code mit Synchronisation sieht so aus:</p>
<pre style="margin-left: 40px;">
private void run() {
...
// try to get both chop sticks
// lock the other guy(s) out
<span style="color:#0000ff;">synchronized (chopSticks) {</span>
if (chopSticks.size() > 0) {
cs1 = chopSticks.remove(0);
System.out.println("Philosopher #" + name
+ " got chop stick " + cs1.getName());
}
if (chopSticks.size() > 0) {
cs2 = chopSticks.remove(0);
System.out.println("Philosopher #" + name
+ " got chop stick " + cs2.getName());
}
<span style="color:#0000ff;">}</span>
...
}</pre>
<p>
Der synchronisierte Block wird wie eine Zeile Code ausgeführt, d.h. alle anderen Threads (und Philosophen) müssen warten, bis wir mit unserem Codeblock fertig sind. Das ist die Holzhammer Methode, sie ist nicht sehr ausgefallen, aber sie löst unser Deadlock Problem. Wenn unsere Philosophen die Stäbchen nach dem Essen zurückgeben, dann werden beide mal dran kommen und beide überleben.</p>
<h3>
Philosopher v.4</h3>
<p>
Dass wir den Ansatz oben die "Holzhammer Methode" genannt haben, deutet darauf hin, dass es anscheinend auch eine vornehmere Methode gibt. Die wollen wir uns jetzt ansehen. In unserer dritten Version verwendeten wir den so genannten Polling-Ansatz: Der Philosoph, der die Stäbchen nicht bekommt, muss weiter versuchen, sie zu bekommen, das ist Polling. Polling ist nicht sehr effizient. Anstelle es immer wieder zu versuchen und zu versuchen, wäre es doch besser zu warten (<em>wait</em>), wenn wir wissen, dass jemand anderes die Stäbchen hat. Wenn aber der andere fertig ist, sollte er uns doch gefälligst benachrichtigen (<em>notify</em>). Das ist es, was der folgende Code tut:</p>
<pre style="margin-left: 40px;">
public void run() {
...
// try to get both chop sticks
<span style="color:#0000ff;"> synchronized (chopSticks) {</span>
if (chopSticks.size() < 2) {
try {
<span style="color:#0000ff;">chopSticks.wait();</span>
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
cs1 = chopSticks.remove(0);
System.out.println("Philosopher #" + name
+ " got chop stick " + cs1.getName());
cs2 = chopSticks.remove(0);
System.out.println("Philosopher #" + name
+ " got chop stick " + cs2.getName());
}
<span style="color:#0000ff;">}</span>
// do we have both chop sticks?
if ((cs1 != null) && (cs2 != null)) {
liveForce = 5;
// now return the chop sticks:
<span style="color:#0000ff;">synchronized (chopSticks) {</span>
chopSticks.add(cs1);
chopSticks.add(cs2);
cs1 = null;
cs2 = null;
System.out.println("Returned chop sticks.");
<span style="color:#0000ff;">chopSticks.notifyAll();</span>
<span style="color:#0000ff;">}</span>
}
...
}</pre>
<p>
Wir fangen damit an, mit <em>synchronized</em> die anderen Philisophen auszusperren. Dann checken wir, ob zwei Stäbchen verfügbar sind. Wenn nicht, dann warten wir. Wenn ja, schnappen wir uns beide und essen. Sobald wir mit dem Essen fertig sind, geben wir beide Stäbchen zurück und benachrichtigen die anderen, dass die Stäbchen wieder verfügbar sind.</p>
<p>
Hypothetisch hätten wir auch einen riesigen synchronisierten Codeblock erstellen und alles darin machen können. Aber das ist eine schlechte Angewohnheit und anfällig für Probleme. Die Regel ist, synchronisierte Blöcke so klein wie möglich zu halten. Das haben wir im obigen Code getan. Testen Sie ob der Code oben funktioniert, Sie werden sehen, dass unsere beiden Philosophen glücklich bis ans Ende ihrer Tage leben werden.</p>
<p>
.</p>
<h2>
Producer - Consumer Problem</h2>
<p>
Sehr oft treffen wir auf ein Szenario, in dem ein Thread Nachrichten an einen anderen Thread sendet. Dies wird auch als das Producer-Consumer-Problem bezeichnet [2]. Ein Thread produziert also immer die Nachrichten und der andere Thread verbraucht sie. </p>
<p>
Ein Beispiel ist ein Webserver: der lauscht immer auf eingehende Requests. Normalerweise leitet dieser Listener-Thread diese eingehenden Nachrichten einfach an andere Threads weiter, um die Arbeit zu erledigen. Denn wenn der Listener Thread auch die Arbeit machen würde, könnte ein Teil der eingehenden Nachricht verloren gehen.</p>
<p>
<img src="images/Ch4_ProducerConsumer.png" style="width: 615px; height: 237px;" /></p>
<p>
Die beiden Threads (Webserver und Listener) teilen sich eine gemeinsame Ressource, die Requests. Wie wir bei unseren Philosophen gesehen haben, könnte das zu Problemen führen. Da dies ein Problem ist, das sehr häufig auftritt, haben sich Leute eine Lösung ausgedacht, die <em>BlockingQueue</em> [3]. Wir sehen uns die blockierende Warteschlange gleich mal im Code an. In unserer Activity erstellen wir eine BlockingQueue und je einen <em>Producer</em> und <em>Consumer</em> Thread:</p>
<pre style="margin-left: 40px;">
public class ProducerConsumerActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// can keep at most 10 elements:
<span style="color:#0000ff;">BlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);</span>
Producer p = new Producer(queue);
Consumer c = new Consumer(queue);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
...
}
</pre>
<p>
Im Allgemeinen kann es mehr als einen Consumer und auch mehr als einen Producer geben. In unserem Fall sind die Nachrichten nur Strings, aber das kann alles mögliche sein, sogar binäre Daten.<br />
<br />
Der Producer produziert die Nachrichten und fügt sie in die Warteschlange ein:</p>
<pre style="margin-left: 40px;">
private class Producer implements Runnable {
<span style="color:#0000ff;">private BlockingQueue<String> queue;</span>
public Producer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
// send ten messages on average one per second
for (int i = 0; i < 10; i++) {
<span style="color:#0000ff;">queue.put("Message #" + i);</span>
pause(DELAY / 2 + rnd.nextInt(DELAY / 2));
}
// end the consumer thread
queue.put("quit");
Log.i("Producer", "Producer is done.");
} catch (InterruptedException e) {
Log.i("Producer", e.getMessage());
}
}
}</pre>
<p>
Der Konsument nimmt sie aus der Warteschlange und konsumiert sie:</p>
<pre style="margin-left: 40px;">
private class Consumer implements Runnable {
<span style="color:#0000ff;">private BlockingQueue<String> queue;</span>
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
// receive messages
String msg;
while (!(msg = <span style="color:#0000ff;">queue.take()</span>).equals("quit")) {
Log.i("Consumer", msg);
}
// we are done
Log.i("Consumer", "Consumer is done.");
} catch (InterruptedException e) {
Log.i("Consumer", e.getMessage());
}
}
}
</pre>
<p>
Wirklich nicht so schwer. Im LogCat sieht das dann so aus:</p>
<p>
<img alt="" src="images/ProducerConsumerActivity.png" style="margin-left: 10px; margin-right: 10px; width: 450px; height: 130px;" /></p>
<p>
.</p>
<p>
Wenn wir mit BlockingQueues in Java arbeiten, haben wir verschiedene Implementierungen zur Auswahl: ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, und SynchronousQueue. Und es gibt auch verschiedene Methoden zum Schreiben, Lesen und Prüfen der Warteschlange. Man sollte hier erst einmal die Dokumentation lesen [3] und danach eine bewusste Entscheidung treffen.</p>
<p>
.</p>
<hr />
<h1>
Review</h1>
<p>
In diesem Kapitel haben wir zunächst was zu TimerTask und AsyncTask erfahren. Dann haben wir gesehen wie man Threads erstellt und ausführt, aber auch die Probleme kennen gelernt die dabei auftreten können wie Deadlock, Race-Condition und Starvation. Gelöst haben wir diese Problem mit Synchronisation. Und schließlich haben wir noch das Producer-Consumer-Problem mit einer BlockingQueue gelöst.</p>
<p>
.</p>
<hr />
<h1>
Projekte</h1>
<p>
In den Projekten vertiefen wir uns den AsyncTask mit zwei Beispielen. Anschließend werden wir sehen wie Grafikprogramme von mehreren Threads profitieren können, dabei werden wir vor allem auch das Thema Synchronisation vertiefen. Zum Schluß sehen wir noch wie wir explizit von den vielen CPU Cores in unserem Smartphones Nutzen ziehen können. </p>
<p>
.</p>
<h2>
<img alt="" src="images/AlarmClockActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />AlarmClock</h2>
<p>
Eine erste Anwendung für den AsyncTask ist ein Wecker. Die UI können wir aus dem ersten Semester borgen. Was neu ist ist der AlarmClockTask. Der wird über die onClick() gestartet, und wir sagen über die execute() Methode auch wann der Alarm losgehen soll:</p>
<pre style="margin-left: 40px;">
public class AlarmClockActivity extends Activity {
...
// UI stuff
btnStart.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
...
long alarmTime = ...;
srt = new AlarmClockTask();
srt.execute(<span style="color:#0000ff;">alarmTime</span>);
}
});
...
// Params, Progress, Result
private class AlarmClockTask extends AsyncTask<Long, Long, String> {
@Override
protected String doInBackground(Long... <span style="color:#0000ff;">params</span>) {
long <span style="color:#0000ff;">alarmTime</span> = params[0];
while (true) {
long remainingTime = alarmTime - System.currentTimeMillis();
if (remainingTime <= 0)
break;
publishProgress(remainingTime);
pause(DELAY);
}
return "done";
}
@Override
protected void onProgressUpdate(Long... values) {
face.setText(convertSecondsInTime(values[0]));
}
@Override
protected void onPostExecute(String result) {
// 100% volume
ToneGenerator toneG = new ToneGenerator(AudioManager.STREAM_ALARM, 100);
// 1000 ms
toneG.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, 1000);
}
...
}
}</pre>
<p>
Die <em>alarmTime</em> wird nämlich der doInBackground() Methode als Parameter übergeben. Über publishProgress() wird dann in der onProgressUpdate() die UI geupdated. Wenn die Zeit abgelaufen ist, wird die onPostExecute() aufgerufen, und dort erzeugen wir über den ToneGenerator einen Alarmton. Interessant ist vielleicht noch zu beobachten was passiert wenn unsere Activty in den Hintergrund geht, oder wenn das Smartphone ausgeschaltet wird.</p>
<p>
.</p>
<h2>
<img alt="" src="images/SpeedReaderActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />SpeedReader</h2>
<p>
Es gibt eine Vielzahl von Techniken wie man seine Lesegeschwindigkeit erhöhen kann [4], eine davon ist die App die wir gleich schreiben werden. Auch hier benötigen wir wieder einen AsyncTask um auf die UI zugreifen zu können.</p>
<p>
Die UI besteht aus einem TextView für den zu lesenden Text, einer SeekBar für die Geschwindigkeit, und einem Button um das Ganze noch mal zu wiederholen.</p>
<pre style="margin-left: 40px;">
public class SpeedReaderActivity extends Activity {
private final int MAX_SPEED = 1000;
private final int INITIAL_SPEED = 500;
private String text = "We hold these truths to be self-evident, "
+ "that all men are created equal, "
+ "that they are endowed by their Creator with certain unalienable Rights, "
+ "that among these are Life, Liberty and the pursuit of Happiness.";
...
}</pre>
<p>
Auch hier wird der AsyncTask gestartet, wenn wir auf den Button klicken. In der execute() Methode übergeben wir dieses Mal aber den Text der vorgelesen werden soll. Der SpeedReaderTask selbst besteht lediglich aus zwei Methode:</p>
<pre style="margin-left: 40px;">
// Params, Progress, Result
private class SpeedReaderTask extends AsyncTask<String, String, String> {
protected String doInBackground(String... params) {
String text = params[0];
String[] words = text.split(" ");
for (String word : words) {
pause(delay);
publishProgress(word);
}
return "done";
}
protected void onProgressUpdate(String... values) {
String word = values[0];
tv.setText(word);
}
}</pre>
<p>
.</p>
<h2>
<img alt="" src="images/ConfettiActivity2.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />Confetti</h2>
<p>
Das Konfetti-Programm aus dem ersten Semester soll das erste Opfer unserer neuen Superpowers werden: Anstelle einfach dumm herunterzufallen, sollen die Confetti sich auch noch ein bisschen zufällig hin uns her bewegen. In unserem ursprünglichen Programm waren die Confetti zufällig gefärbte GOvals. Ähnlich wie wir GRects in der SliderThreadingActivity zu Sliders gemacht haben, verwandeln wir nun die GOvals in Confettis, die das Runnable Interface implementieren:</p>
<pre style="margin-left: 40px;">
private class Confetti extends GOval implements Runnable {
public Confetti(int width, int col) {
super(width, width);
setFilled(true);
setFillColor(col);
}
@Override
public void run() {
// animate the slide across the screen
for (int i = 0; i < HEIGHT / STEP; i++) {
pause(DELAY_MOVE);
int x = (int) (Math.random() * 2 - 1);
move(x, STEP);
}
}
}
</pre>
<p>
In der run() Methode bewegen wir das Confetti um den Betrag STEP nach unten und lassen es auch ein kleines bisschen nach links oder rechts wandern. In unserem GraphicsProgram erstellen wir neue Confetti wie im ersten Semester, und machen dann aber daraus Threads:</p>
<pre style="margin-left: 40px;">
public class ConfettiActivity extends GraphicsProgram {
...
public void run() {
waitForTouch();
HEIGHT = getHeight();
while (true) {
// create a new random confetti
int width = rgen.nextInt(SIZE / 2, SIZE);
int col = rgen.nextColor();
double x = rgen.nextDouble(-SIZE / 2, getWidth());
double y = rgen.nextDouble(-SIZE / 2, 100);
Confetti confetti = new Confetti(width, col);
add(confetti, x, y);
// run the confetti in a new Thread
Thread confettiThread = new Thread(confetti);
confettiThread.start();
pause(DELAY_CREATION);
}
}
...
}
</pre>
<h3>
Observations</h3>
<p>
Der Code, wie er ist, läuft gut, aber wenn wir ihn eine Weile laufen lassen, werden wir einige interessante Beobachtungen machen. Wir stellen fest, dass sich das ganze Konfetti am Boden sammelt. Das sieht gut aus, aber es ist ein Problem: Das sind alles tote Threads, und der Müll wird nicht eingesammelt (garbage collected). In einem lang laufenden Programm führt dies früher oder später zu Speicherproblemen.</p>
<p>
Wenn wir die Anzahl der pro Sekunde erzeugten Konfetti erhöhen, dann friert das Programm irgendwann einfach ein. In einer ersten Schätzung könnten wir denken, dass das mit der Tatsache zu tun hat, dass wir mehr Konfetti haben, als wir pro Sekunde zeichnen können. Aber das ist nicht richtig. Der wahre Grund liegt in der sehr schlampigen Art und Weise, wie wir die draw() Methode der GObjects in der onDraw() Methode der GView Klasse aufrufen.</p>
<p>
Noch etwas anderes: Lassen wir die App ein wenig laufen. Dann schicken wir die App in den Hintergrund, indem wir etwas anderes auf unserem Handy starten und ein wenig warten. Dann kehren wir zu unserer Confetti App zurück: wenn wir genau hinsehen, bemerken wir vielleicht kurz den alten Zustand der Benutzeroberfläche, und dann tauchen auf einmal ganz viele neue Confettis auf. Das heißt, obwohl der UI-Thread nicht lief, wurden weiterhin im Hintergrund Confettis generiert. Jedes Mal, wenn wir einen Thread starten, einschließlich des Haupt-Threads, läuft der so lange weiter, wie er in seiner run() Methode ist. Daher ist unser Ansatz, eine Endlosschleife innerhalb der run() Methode durchzuführen, nicht besonders schlau.</p>
<p>
.</p>
<h2>
<img alt="" src="images/SnowFlakeActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />Snowflakes</h2>
<p>
Am Ende von Kapitel vier im zweiten Buch sahen wir die KochSnowflake. Stellt sich heraus, wenn wir die Confetti im obigen Programm durch Schneeflocken ersetzen, bekommt das Ganze eine Weihnachtliche Atmosphäre. Eine Möglichkeit, eine Klasse SnowFlake zu erstellen, ist mithilfe des GPolygon. Wenn das mit GPolygonen aber nicht funktioniert, kann man alternativ auch nach Bildern von Schneeflocken suchen und die GImage-Klasse verwenden.</p>
<pre style="margin-left: 40px;">
private class SnowFlake extends GPolygon implements Runnable {
...
public SnowFlake(int size) {
super();
createKochSnowflake(0, 0, size, NR_OF_ITERATIONS);
}
private void createKochSnowflake(int x, int y, int length,
int nrOfIterations) {
drawKochLine(x, y, length, 0, nrOfIterations);
drawKochLine(x + length, y, length, -120, nrOfIterations);
double x1 = x + length * Math.cos(-60 * Math.PI / 180);
double y1 = y - length * Math.sin(-60 * Math.PI / 180);
drawKochLine(x1, y1, length, 120, nrOfIterations);
}
private void drawKochLine(double x0, double y0, double length,
double angle, int nrOfIterations) {
// base case:
if (nrOfIterations == 0) {
double x1 = x0 + length * Math.cos(angle * Math.PI / 180);
double y1 = y0 - length * Math.sin(angle * Math.PI / 180);
addVertex((int) x1, (int) y1);
return;
// recursive case:
} else {
double len = length / 3;
double ang = angle;
drawKochLine(x0, y0, len, ang + 0, nrOfIterations - 1);
double x1 = x0 + len * Math.cos(ang * Math.PI / 180);
double y1 = y0 - len * Math.sin(ang * Math.PI / 180);
drawKochLine(x1, y1, len, ang + 60, nrOfIterations - 1);
ang = ang + 60;
double x2 = x1 + len * Math.cos(ang * Math.PI / 180);
double y2 = y1 - len * Math.sin(ang * Math.PI / 180);
drawKochLine(x2, y2, len, ang - 120, nrOfIterations - 1);
ang = ang - 120;
double x3 = x2 + len * Math.cos(ang * Math.PI / 180);
double y3 = y2 - len * Math.sin(ang * Math.PI / 180);
drawKochLine(x3, y3, len, ang + 60, nrOfIterations - 1);
}
}
}</pre>
<p>
.</p>
<h2>
<img alt="" src="images/FireWorksActivity.png" style="margin-left: 10px; margin-right: 10px; width: 184px; height: 355px; float: right;" />Fireworks</h2>
<p>
Eine weitere schöne Anwendung von Multi-Threading ist ein Feuerwerksprogramm. Ein Feuerwerk besteht aus Raketen, die explodieren, und dann folgen kleine Lichter den Gesetzen der Schwerkraft. Wir ignorieren die Rakete und kümmern uns nur um die kleinen Lichter nach der Explosion:</p>
<pre style="margin-left: 40px;">
public class FireWorksActivity extends GraphicsProgram {
...
public void run() {
...
while (true) {
<span style="color:#0000ff;">startExplosion();</span>
pause(DELAY);.
}
}
...
}
</pre>
<p>
Bei der Explosion erzeugen wir eine feste Anzahl von Lichtern (GLight), jedes mit der gleichen Farbe, alle an der gleichen Stelle beginnend, aber jedes mit einer anderen Richtung:</p>
<pre style="margin-left: 40px;">
private void startExplosion() {
int col = rgen.nextBrightColor();
int x = rgen.nextInt(0, getWidth());
int y = rgen.nextInt(0, getHeight());
double angle = Math.random(); // start with a random angle
double deltaAngle = 2.0 * Math.PI / NR_LIGHTS;
for (int i = 0; i < NR_LIGHTS; i++) {
double vx = SPEED * Math.cos(angle);
double vy = SPEED * Math.sin(angle);
GLight light = new GLight(vx, vy, col);
add(light, x, y);
Thread thread = new Thread(light);
thread.start();
angle += deltaAngle;
}
}</pre>
<p>
Aus jedem Licht wird ein eigener Thread. D.h. bei vielen Explosionen gibt es viele Threads! Die <em>GLight</em> Klasse selbst ist trivial:</p>
<pre style="margin-left: 40px;">
private class GLight extends GOval implements Runnable {
private static final int DELAY = 40;
private static final double GRAVITY = 0.05;
double vx;
double vy;
public GLight(double vx, double vy, int col) {
super(SIZE, SIZE);
setFillColor(col);
setFilled(true);
this.vx = vx;
this.vy = vy;
}
@Override
public void run() {
// animate the light
for (int i = 0; i < 100; i++) {
pause(DELAY);
move((int) vx, (int) vy);
vy = vy + GRAVITY;
}
// make the lights invisible:
setFillColor(Color.BLACK);
}