From 6dd204d542a764d24b4eb71af009c5ab88fa27b6 Mon Sep 17 00:00:00 2001 From: Tobias Jordine <jordine@hdm-stuttgart.de> Date: Thu, 9 Jun 2022 22:01:21 +0200 Subject: [PATCH] Code Demos Concurrency inkl. Bewertungskriterien --- .../vorlesung/clientserver}/EchoServer.java | 2 + .../vorlesung/clientserver}/GreetClient.java | 2 + .../issues/ConcurrentModificationDemo.java | 27 ++ .../concurrency/issues/DeadLock.java | 60 +++ .../concurrency/issues/LostUpdate.java | 43 ++ .../ConcurrentModificationSolutionDemo.java | 27 ++ .../solutions/DeadLockSolution.java | 60 +++ .../concurrency/solutions/FutureDemo.java | 53 +++ .../concurrency/solutions/JavaStreams.java | 32 ++ .../solutions/LostUpdateAtomic.java | 45 ++ .../solutions/LostUpdateSynchronized.java | 46 ++ website/assignments/bewertungsbogen.xlsx | Bin 19521 -> 19741 bytes website/concurrency.html | 419 ++++++++++++++++++ 13 files changed, 816 insertions(+) rename src/main/java/{ => de/hdm/jordine/vorlesung/clientserver}/EchoServer.java (96%) rename src/main/java/{ => de/hdm/jordine/vorlesung/clientserver}/GreetClient.java (96%) create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/ConcurrentModificationDemo.java create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/DeadLock.java create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/LostUpdate.java create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/ConcurrentModificationSolutionDemo.java create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/DeadLockSolution.java create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/FutureDemo.java create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/JavaStreams.java create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/LostUpdateAtomic.java create mode 100644 src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/LostUpdateSynchronized.java create mode 100644 website/concurrency.html diff --git a/src/main/java/EchoServer.java b/src/main/java/de/hdm/jordine/vorlesung/clientserver/EchoServer.java similarity index 96% rename from src/main/java/EchoServer.java rename to src/main/java/de/hdm/jordine/vorlesung/clientserver/EchoServer.java index 0cd7922..0191f52 100644 --- a/src/main/java/EchoServer.java +++ b/src/main/java/de/hdm/jordine/vorlesung/clientserver/EchoServer.java @@ -1,3 +1,5 @@ +package de.hdm.jordine.vorlesung.clientserver; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; diff --git a/src/main/java/GreetClient.java b/src/main/java/de/hdm/jordine/vorlesung/clientserver/GreetClient.java similarity index 96% rename from src/main/java/GreetClient.java rename to src/main/java/de/hdm/jordine/vorlesung/clientserver/GreetClient.java index f3c76ce..d9b1add 100644 --- a/src/main/java/GreetClient.java +++ b/src/main/java/de/hdm/jordine/vorlesung/clientserver/GreetClient.java @@ -1,3 +1,5 @@ +package de.hdm.jordine.vorlesung.clientserver; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/ConcurrentModificationDemo.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/ConcurrentModificationDemo.java new file mode 100644 index 0000000..0ed2f17 --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/ConcurrentModificationDemo.java @@ -0,0 +1,27 @@ +package de.hdm.jordine.vorlesung.concurrency.issues; + +import java.util.*; + +//based on https://www.geeksforgeeks.org/difference-traditional-collections-concurrent-collections-java/ + +public class ConcurrentModificationDemo{ + + public static void main(String[] args) throws InterruptedException { + Collection<Integer> asyncCollection = new ArrayList<>(); + Runnable listOperations = () -> { + for (int i = 0; i < 100; i++) { + asyncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6)); + } + }; + + Thread thread1 = new Thread(listOperations); + Thread thread2 = new Thread(listOperations); + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + System.out.println(asyncCollection.size()); + } + +} diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/DeadLock.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/DeadLock.java new file mode 100644 index 0000000..3eccef3 --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/DeadLock.java @@ -0,0 +1,60 @@ +package de.hdm.jordine.vorlesung.concurrency.issues; + +// based on https://www.tutorialspoint.com/java/java_thread_deadlock.htm + +public class DeadLock { + + public static void main(String[] args) { + + Object lock1 = new Object(); + Object lock2 = new Object(); + + Thread t0 = new Thread(new Runnable() { + @Override + public void run() { + synchronized (lock1){ + System.out.println("Thread 1 has lock1"); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("Thread 1 waiting for lock2"); + + synchronized (lock2){ + System.out.println("Thread 1 has lock2 and lock1"); + } + } + + } + }); + + Thread t1 = new Thread(new Runnable() { + @Override + public void run() { + synchronized (lock2){ + System.out.println("Thread 2 has lock2"); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("Thread 2 waiting for lock1"); + + synchronized (lock1){ + System.out.println("Thread 2 has lock1 and lock2"); + } + } + } + }); + + t0.start(); + t1.start(); + + } + + + +} diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/LostUpdate.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/LostUpdate.java new file mode 100644 index 0000000..5c3d45d --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/issues/LostUpdate.java @@ -0,0 +1,43 @@ +package de.hdm.jordine.vorlesung.concurrency.issues; + +public class LostUpdate { + + private int counter = 0; + + public void increment(){ + counter++; + } + + public static void main(String[] args) throws InterruptedException { + + LostUpdate lu = new LostUpdate(); + + Thread t0 = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 10000; i++) { + lu.increment(); + } + } + }); + + Thread t1 = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 10000; i++) { + lu.increment(); + } + } + }); + + t0.start(); + t1.start(); + + //t0.join(); + //t1.join(); + + System.out.println("Counter value: " + lu.counter); + } + + +} diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/ConcurrentModificationSolutionDemo.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/ConcurrentModificationSolutionDemo.java new file mode 100644 index 0000000..789553b --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/ConcurrentModificationSolutionDemo.java @@ -0,0 +1,27 @@ +package de.hdm.jordine.vorlesung.concurrency.solutions; + +import java.util.*; + +//based on https://www.baeldung.com/java-synchronized-collections + +public class ConcurrentModificationSolutionDemo{ + + public static void main(String[] args) throws InterruptedException { + Collection<Integer> syncCollection = Collections.synchronizedCollection(new ArrayList<>()); + Runnable listOperations = () -> { + for (int i = 0; i < 100; i++) { + syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6)); + } + }; + + Thread thread1 = new Thread(listOperations); + Thread thread2 = new Thread(listOperations); + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + System.out.println(syncCollection.size()); + } + +} diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/DeadLockSolution.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/DeadLockSolution.java new file mode 100644 index 0000000..72c0061 --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/DeadLockSolution.java @@ -0,0 +1,60 @@ +package de.hdm.jordine.vorlesung.concurrency.solutions; + +// based on https://www.tutorialspoint.com/java/java_thread_deadlock.htm + +public class DeadLockSolution { + + public static void main(String[] args) { + + Object lock1 = new Object(); + Object lock2 = new Object(); + + Thread t0 = new Thread(new Runnable() { + @Override + public void run() { + synchronized (lock1){ + System.out.println("Thread 1 has lock1"); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("Thread 1 waiting for lock2"); + + synchronized (lock2){ + System.out.println("Thread 1 has lock1 and lock2"); + } + } + + } + }); + + Thread t1 = new Thread(new Runnable() { + @Override + public void run() { + synchronized (lock1){ + System.out.println("Thread 2 has lock2"); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("Thread 2 waiting for lock1"); + + synchronized (lock2){ + System.out.println("Thread 2 has lock1 and lock2"); + } + } + } + }); + + t0.start(); + t1.start(); + + } + + + +} diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/FutureDemo.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/FutureDemo.java new file mode 100644 index 0000000..07fd8ef --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/FutureDemo.java @@ -0,0 +1,53 @@ +package de.hdm.jordine.vorlesung.concurrency.solutions; + +import java.util.concurrent.*; + +// based on https://www.baeldung.com/java-future + +public class FutureDemo { + + private ExecutorService executor; + + public Future<Integer> calculate(Integer input) { + executor = Executors.newFixedThreadPool(10); + return executor.submit(new Callable<Integer>() { + @Override + public Integer call() throws Exception { + Thread.sleep(1000); + return input * input; + } + }); + } + + public void shutdownExecutor() throws InterruptedException { + executor.shutdown(); + try { + if (!executor.awaitTermination(800, TimeUnit.MILLISECONDS)) { + executor.shutdownNow(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + } + } + + public static void main(String[] args) throws ExecutionException, InterruptedException { + FutureDemo demo = new FutureDemo(); + System.out.println("Starting calculation"); + Future<Integer> result0 = demo.calculate(2); + System.out.println("2 x 2 = " + result0.get()); + + Future<Integer> result1 = demo.calculate(4); + while (!result1.isDone()){ + System.out.println("calculating..."); + Thread.sleep(300); + } + + System.out.println("4 x 4 = " + result1.get()); + + demo.shutdownExecutor(); + System.out.println("exit"); + System.exit(0); + } + + +} diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/JavaStreams.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/JavaStreams.java new file mode 100644 index 0000000..cb59273 --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/JavaStreams.java @@ -0,0 +1,32 @@ +package de.hdm.jordine.vorlesung.concurrency.solutions; + +import java.util.stream.IntStream; +import java.util.stream.Stream; + +//based on https://mkyong.com/java8/java-8-parallel-streams-examples/ +public class JavaStreams { + + public static boolean isPrime(int number) { + if (number <= 1) return false; + return !IntStream.rangeClosed(2, number / 2).anyMatch(i -> number % i == 0); + } + + public static void main(String[] args) { + + long start = System.currentTimeMillis(); + + long count = Stream.iterate(0, n -> n + 1) + .limit(1_000_000) + .parallel() + .filter(JavaStreams::isPrime) + .peek(x -> System.out.format("%s\t", x)) + .count(); + + long end = System.currentTimeMillis(); + + long duration = end - start; + + System.out.println("\n\nDuration: " + duration/1000.0); + } + +} diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/LostUpdateAtomic.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/LostUpdateAtomic.java new file mode 100644 index 0000000..5fb6d2d --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/LostUpdateAtomic.java @@ -0,0 +1,45 @@ +package de.hdm.jordine.vorlesung.concurrency.solutions; + +import java.util.concurrent.atomic.AtomicInteger; + +public class LostUpdateAtomic { + + private AtomicInteger counter = new AtomicInteger(0); + + public void increment(){ + counter.incrementAndGet(); + } + + public static void main(String[] args) throws InterruptedException { + + LostUpdateAtomic lu = new LostUpdateAtomic(); + + Thread t0 = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 10000; i++) { + lu.increment(); + } + } + }); + + Thread t1 = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 10000; i++) { + lu.increment(); + } + } + }); + + t0.start(); + t1.start(); + + t0.join(); + t1.join(); + + System.out.println("Counter value: " + lu.counter.get()); + } + + +} diff --git a/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/LostUpdateSynchronized.java b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/LostUpdateSynchronized.java new file mode 100644 index 0000000..d2929ca --- /dev/null +++ b/src/main/java/de/hdm/jordine/vorlesung/concurrency/solutions/LostUpdateSynchronized.java @@ -0,0 +1,46 @@ +package de.hdm.jordine.vorlesung.concurrency.solutions; + +public class LostUpdateSynchronized { + + private int counter = 0; + + public void increment(){ + counter++; + } + + public static void main(String[] args) throws InterruptedException { + + Object monitor0 = new Object(); + Object monitor1 = new Object(); + + LostUpdateSynchronized lu = new LostUpdateSynchronized(); + + Thread t0 = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 10000; i++) { + lu.increment(); + } + } + }); + + Thread t1 = new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < 10000; i++) { + lu.increment(); + } + } + }); + + t0.start(); + t1.start(); + + //t0.join(); + //t1.join(); + + System.out.println("Counter value: " + lu.counter); + } + + +} diff --git a/website/assignments/bewertungsbogen.xlsx b/website/assignments/bewertungsbogen.xlsx index 1280b71ec19b6063d00fa9d930e77be9327a9ce1..1936851b814da32e84308bccbb79fd9641006984 100644 GIT binary patch delta 6166 zcmZ9QRb12$m&S(%>5iehJBF6-p(F)KLAqf8;YTAfltZV2Ae~Bghk{5-cSuXuu>XDc z-QCabxj8rIoXh7t&$lNNB`+JLRv!nW^Ckta24Rdt4-`SDLadyg(Edt`WonU-2|J^{ z;r?bMdh|6Z4GnrqCg_sGKJx~7AIhFl^5ad~(Z~J(1|Y)+Z&M(rwf<G+3ky*pZvk5S zI(AA74`ZRb(1PDe_FOtS5ypi!LP4*eg=9TE&cXP$&K2J|;(}<%iwq9Ldri05D#}`k z_*|?VxBz<{G2iWH6~@Bmr5}VWO)??ScP^vguPX1hB&lp$wj{NbS7s_FUlNUQm-oCi z`fGeN`R9SQIeT=s`kRk_6R2*De6B{8v;L1vtGSrsFBJ@TkEQMf^_%F^06NZLBx|OW zWcf}DnI`Wg#`DdpNe*Nif&X#<(|qpWnhYz0))mmJLqKRea*8mH#;QEU|L%vR`hnqm zX%WW=D^SKIXe1V;76=@mZ=C-2W^v`$f_=H7u}T^gx7v)j2*r0Gy8|D6_=BbN5e%cD znv@7@eI$I`$7vE8Pc5h@T)ZPQM<;4KU4v?1VVpe@<19{^fJt!t_Rg?VXE02b2cvfJ zZV*83!vIcC+=r+SJH(cyk~76KBiyPtZKy+a6u8hCLMDY8ypz|TPUki-SBvS56@@m4 zB1|kcDVcamS{<V2Tt6{NWpWRgB<O$WRr4+RvT(}~Rnij2A=ljC2H=5VY(=t8yXNLU zJ2k7ByaFyTeqq~l`Ez#(2mZ~^wopBungGHj&ei0of3Hjv9*LgG22*{m&MRhR>|<m> zn|Q()9~K<N_*}mv)c<^F7)vTwd->;&&_Tb#L`n~b2Vegrw?Bjfp~o3s^Gnf$xm7dZ z4DGoEnZG4O2P;K$A;Cluz(MFn3S}#tn3^E3Z{B3kgw6CIrsrp=Dv6yDe8mjmS_O=( z)#;p#)4a8Z0-F(LA#P$FnmYy$))O?x{yucq#F%EvJ4As-&4Zsdx0_H)tIfxa@wWWt zH|dU;LT0ZWx4(`mXqNoxQL9!`9Y+17kqqtS*6Hb(zDh}pXFvIjPBLzN34P;QRFjqu z|Ag7=;~7GO_a#w+xv`)n>!^)oC?9|-trC3rj*ICotQGlvYq>q4FhLS_JEG<A?tU45 zH7ap;aG%1}u)JsE*+MrTeVM0S=n)q;;(iVAwMRC&4UAjl%I3Cs`Um>}K06Bs7s!2~ z1H+q_9zv`e5O8D|%qbZrjw3wLiDxk6uaq?ZsUW4hfOrFID>;E?>vH{kji>2aL&pcg z;rKYz<9Y8873@W7IojT?Sy@@g(;U~NeSk0Qa4FMsKan!hh47Lk2Nr^UiFl)fK(WLi z5IzV5g1HL#_y)SZ_Th)QxqdfY_1J(Ah05<Ev3O3LLX&I57%Ph1e0h$ljJY|MD>P_p z|I7~yxy?@@Pb3=AG8<FrU+XT$S|=S%h@2-l9wy2+iYXB)i@!LHqqK~sXKp8A+m4UB zp5wj7qIV|SVRMK85OGaR3D-jRk4;`h;_xLt9DQs?n8;bWW|gYKPckio%^l53?AR2| zVH%#jQI(&StUa%bC}()z_0=*=XL?UC!~bmXF^Z*MAEa5m3yLPsGgO@!v<@ugS1q8l zP?vH1=nQ6Se(pA%A>_R6Ps$R~sH=m^MWbKF(~YleG#McSm;_Map;;WJfNo_zbK>{j zbYB_g$EBiwkkQjA$Vj+t9e0`es94#8Diy$NuO^qlc*sFtUuh9f@<4{VuOLp)I`F%h zWU1#}_p76kNm<GynK?TOGu^Sl9|0F()auOm2H5PZ21F<zS614&{_*BP_&D3l6t{(8 zW8H=+uJ%t~;N1Bc8BM|=!Ikt}1M*A9EXS2FvNhG_i}?M>tM*iC0WYOM4Rcf>iJ!im zZeP0DLa>}cuAUv>3sY80GFY=bzPC^TERa6COG)kfsloKTwx#Xw=ltIs_suRm=+5H1 zTX&l>APY2CG?2B_I!l`OO!!U;;xX{5f8@|tZ!X^rFiz%%kWu2=?c`;4B*RDt${H92 z!48`($!ugI5-8^S;trn;h@wC6r{9^xk-yzl<Wj%+-TWEG1aXieb~o|G7yQBqU*3<n z4*1pw?JjThCgDki(zQtq{b8s2CB~P31VYxSoK31=*BHB=`&mxJeb<o{dr4JxwcRNp zc6D+O6i`{7X7Aa8q_QwpKkI$gD_O}=_SY_05h&5U%Qi&IVAjg)GxDW3E3JaoV{V^e z&$P|Vp0s-Vi=B0EW?vy}&e0D9$+V*(gN6K80<jz5OP}6W;2EysAm%plOS;NUSO1{P zi!(Wgnb>RR4|vz^KqR+7-g9fDkzHGS`VVZt@#Jh82ik0*?%@)R;JWxmeC#al^|3hl zLi)5!AW$kcyK>Q3w|R2tWBf*ZRuInjc&cmCh;8N|$)SCDhI7(qGMeq#DgHTis5;Ff zb;(9~K`=f104}Qiik1F0H-(52m(f&R$-8nM%E-AWjq+q}b4hc<()6eK3Vo`(pX!&u z(APr__~9O*kJiHMx_hjJAUnNA95U=>lzr-|$NGBa_shZ513ZG*5RI`9dmP7Fkco7q zJ;||k{D(h(mBzxz9jiiK9$BD`^DI8H^}@LyUy04d=Sia!v68<M5nIHEwujv78vOFZ zSM_h`oW%Ns*WbU`)V0?Jot~pWOS7T@VAVYW=3397U3sBYD@!|d^4MR{<o8pi8A_(W z@;cRDBqVMH;e7`T;}y4b9uDWju~EcqMKL{N=Kh2JqpL@+NhkJAFLgRmzF98EddWYe zs(vcC?j6>>gRK$Xbxco8adz%=Of8D)(k)Mktj^jHsa&O1r|GJ_pGA*w9h*rfW-@uk zs&Gxt62V8>gOum|{V!E&=q9eg!6;99?C?VPf<<xM6qke-($W(25p`e&ln%rsn4I`{ zoL|G5j>m`H4~P19B-@+`;u<W=P;slOuunsR_CfN(W3I5CZjqm1AOqYuU@9$-Md6Wy zFr*bku|h=AY6Iq5sKFCz<W!u(f5XQa1nfvG`TooznXFw1XT$BrIFh^RK4P<lPt8zr zF5#<Zr6e_<y>z+k&x)!+)~~VaoYGvi5xQbn&A;-<{o=0sc=^)#@v!MQMMBl8(fF*B zDqsVHCVHaO8km84#qk%d?5wvoyfv|!$0Fs;iU#um?**5t^vUS9^=cD*lY;JX`kjwF zuW6@t?>+KQJbJv_KfGbnZpWJ=k@Uh#?baPbzfVU1bWA||n{Y@#cl@ca&PHm`7D;6r z=?YRK@Zs-FqSrC)7n_BKTtPXP6iC3`MVcvBP}$5wAtEOMQ8eF=PeB<u!?Z?f>|{dO zMddqWBb1SFtT)Xnr+&e;T;1$j%Ac3e?|)g0g%K*&V=1|>H^C~&MK#0sKnMaoJ^k|+ zo}NDEylh~kzsA4`x6E?5Lj!>n5sa*?fPPYuIML85&!x2KlF};@3@}M|j<S@SpNCs< zqXC01dTzoAp6Z(Y@B53qIwl+497A3EVi{I*)Om+fIB@GS*yfC@vc?GxwFr)F-O_2K zR9X1`ENiB9Qd(rpw<jt3Z|Gw&xC{_AIPn)IUCJAu`1-U-J?Wqr2!>5)o;wr(#)`!P zch<ux@w1h(DOz@Ibyhnn<{f{S0-b+-Q|o9E69Qh_HAqPzc8W=qcU(?<4*GT*Y~;?> zOZ@UA{^pnl!}&u1=Pp@Pac-DMVz^TZx7htR6YaEvnq|{%Xw@D&mWS{+B=;~S-`HR~ zXRWz9i?f6Xs-c3?RvFZXOVbXpoUxkA@1b;P<yCR;%y+4YsaPopN8|8_$rqh=#7%0( zl5h8((6KexSEVV_%6A7>5va<1G*mH{AMq~fA3qf=NekRFhgU-)7ag;5WC=qK7jSRC zN-PoFiz8n1n?tLzid?shxt(6!yxM8uWO%PSk$><t`}dqrq%L$A%AE!{6(4-Nw!vah zklomrX%eQvSpS7dJKN$K#pO%TqfV$=pa^%c{@qO=4X)=&I*U%;xy#!7h2ghoA8X!S z7|<ib*AuYL@5ePBZy(lL<!<BO97XEOk<rXd)N59bH-w`@B(7-OB6EdyCb^fP%Qo~8 zDqbOZvEWs4n-bu#95MzlkPDxJYqgL|p(qDW^Jz~?If)$g&bKFboX9kXC(cyo+>H4L z38H(|^C98Ig9RNpZ%GvDPjuvbYQLx2IxTd>Tg>CulauNJn?59B*j=u;oY?>Q>c*1m zt<_!NrXAs{i-e>wLw$FO`4vmMj1Jk`-OzNpK}zCRiOMiyj0}Lt;mkYC6Mas$_b~Ak zpQSzb!$9&Hqz+{09}-c-Lqwav1~Ii@hFBxB*05`WBO;}Hnsfxqk2NCg^tM6Kw|D3- z%^1ph&eS6^Mg~M9_+p&UHT`rwVH`IxIEi7i^wz0XA1S@x-P}E}o9M0C^LF7OA|<BQ z!fZ`4Fa<VIx`24;E{W=HIidqrDja{32Lkk$h)pucsTv8bkn%ZdPb4oy0~1}Ki(`J) zSMx!cEkIztmWtM>HvAW+UWue0SFSAtWXuDftD3Vw3XvF<uBshL_Q%JU3g>KG1rsRM zB^=uZ_u1m!3_3<Jd0?*-^k-nsO;B2pLq6EdqtXs|0wfP%>k7Tecv_7&<EQu{cwCAg zHI6>Lb`@yM>L+_2`S=uUEIVSTXU+8wc(_pIloWM|arJ#6UyI1wK0Ldt?s3H>1-uts zO0!yhXVEW-5_N(R@i|@L46itsN`cs$Mg$IK8-5;<lCXl1QYb^r{)C`RERgGt{6_$u zp3p!D3J5`iG-n41#(}V%kZHrC6gvuFrPU`$Hn57(r8E+p)97Qz<A%i@?9JKy>4xIN zBHzLTQ=I?(RmqXDG#QgX%kGlZcn7N$y)xa~@H&eTR5pD$YVr#q{s4>xcjI+hj9KCl z^(t1fJ!}fTv4e`-KX7g|T~#N+#7KM@jRp80)T6v9>cRA51tM@@?1gPHoN*km`a$5L zC_hZ#Ni!tE+fA%W+ruX~jYgc83@1>3boxWk%^5?3uI}hX!ge!s(2afXsPqM%8_`yD zC6c$1_#1w;u_k9KN0n0&m#snBx2t=4aJSw97Tc;{CdYWO19zAPd5xx?jNXJOCaQh& z2gK^!k4!ARteb|sf0XGP8}S>!D!HLd^X7t*NVkha_f#Z`bvR+hWl?Tg#>n!cL-eR9 zw8TaR7hz92(U1eta`bRZj7N&QS`mE_eIl9q$~G9UQ$JjS)K=>k`FQAU6`K4Pj39!L zJ*zJG%$bJ;0u}Nil4Yp?OT$eFMaXLTBVoCWs{*-Km_v|5{c}nNoq)!02aUaefle>c zy4M-3q}I->SHR)eLrmZXAK*HkmX{1N%qhQMm5E|zN{sg7J;)C}PPvj_DvoPrvok6o zbe8V%*GVR5P~X4#=szV{@$`apR)cPYb;?(-jpOCL=goE3QLGt|{lVXO4RwKj`fhyt zhiYF;h-NW<eXi*Eg)l-hR*6)?)m>e+QFrUt=eo9z$IcYZVwY|5Y$wQsih&cR$}6># z7T@v;lI`QiZ_dp4IaQ{Ps{W*)4v|gV)htR%sL>nYoJ)%^T(SCBy3rS?tK+ZwepYRy zl+hiJ^<IkD4KWY^zz7~@+!c5`uI~)%YiZ1mg_(pTQA`llw}!lI&c&v}NSeWgtJlz( zUFj)avQDI5+g;h_NpR?5Xwc#cmo5@3nRfXTTI4EEyB|3A{z_qKbnD}l4?dPny1i;< zbA0H;=+j2c53>@>-<2%)X51l*oucXGH32s{F<qtLRYxD7E4-6t$!uoz`Xru1acW1n zf3we`&fdLj(8tZl(j&h0MiDl;p>D^nX8(*1L7^|s3H!?<>>hpU4?1amN2Wym=#XF3 z{zID~Tq`J=C;;Vb*jGa`Wy9|iNj-tGEqJ$HE6(!S*S>(~UPW~I6lLg1dTSa`d4sw$ zB3i6Mj=oT!@VPa<nvE6DPW^9=t`@e=6zaW6yOrQ=sZ1J-fJm-BG>^9j%KIoy+xJOn z6$N3Cv<8Gm!Z-vDCU=psgX_kNU}mNRv+y&^xHG@{AX~V2{g@u}W>#$tExv^fdI^Ek za#~5YC)xy`i7@SO=EL_mQWdrzuGN{Q+%GO0MWO*L`A?+Z&xR*<1meD?`+l}}wa@M( zH@6>m0%Se%W^3TT2)Rw(Y2j?pGjrytb@`%RlTF*YgGQ1s8OQDSgo`}w(`K8@K45>3 zr3##oZI;|$Ki{}Gb=M&T!rkwDf=X|!>_hh6|NR)MFdKoIjv%m#9kjK@OE8=fv9}#y zE@T1hc#up}4+R_O<?JP_@dLbU1F6>}?=2at^&LN7_)3}Pe_z9U!k;0yRFE2e&|qv; zb>0G{(;2o-8g@se?ciO{Zmf4U5nLC?NY4>&H0x=w@_$xMZZN0BpA0A3kvyY#AvJ`= zedkg9ws{hpi=K}ss7Q^4zy2KwgDeN9J^n9%y}v!im(o#zr<q_ufrH3s$LjAMv|i>S zInvE~ne=d?es~$wh*8e42YN(z8=rrg+o*f$le)@axaX)E9NEvoIS1izrz{7RMg&6u zIRvv~HKZ;0Z&^VFee|`BqI&Lz<WF~P&wFNbf)@-U6O8lU2~83Uhuq?@0p-!GJY`@} z-#uD28taP^ts$!pp<F=%4tz!eiifk4LZCc`8!@Gc2}?Xpn-&^yxF}`0LGQ`+J$>yZ z1?wt_EdGU(abGy)NcooiTtF?qlmEWOl%GjfpNw7N%L|ANK|}Kp`?pW;GEivaVwq#Q zFm~TYr`8RTBcIE1Qq``QgC7Yt(Nlmo<-v7|#Xmv61x_td(OGk4!j21%C~Sq+GY8Bn zNk3|?*>Wh7C#G5uL~ZBz$3Qd)Rl8gYs(znSadW@?8-ar^tLj9%@LDPA=9)GwU)#vr z%KY-=szm4ZnkvjVTO{;EF{xv?y30_-GGZ7;gF7MhpuHHH{Bxni`$C@Y{^kh?#Myo8 z)qCru0~zDU)s?7{_#~I<<WT!=(vH%Hq;A&=Tit`D*OZ|a<}g)VD;HjG_biFmTxeS` z=(#s6I%F`4+lu{TTnT61(mAg&S_%Evs0oHiZwrJ{Nn}C=o_Z~78fy-7c(qoyt)OFF z#N-k6bRHEnn6SlG7&|4Yy>tNJ?Am!T%bmuyJU-$RwL)gvxwVMl6(Qg!Dxhn@J5DNJ z6==IJqkuZ5XD`UN(0;W|``#pV*3j?IdzUE0L5^V$E=<WFT{*=Vdi!1@+(uS~c(B6$ zo3mhw_ZKzNOO)Ob6iLGB40cTmz%?LyL(2hg`dQpquPprhb{D!^o=6U;ZTXzQzB2VZ zJ186AADOmy&*C#}PtNSDDDhZR*^Gj!D>-QxezxlnldfP*of^zcq#?Ck$Tdf~6?A0s zZGo&lwD07QpZ*{}ky2!Q<7ytr@{;@&dP4xM1b@SitFDQIFGGe8MI~F;+()F<(MW+q z>e?CMXJjau4jTUN9aA;|>ijM9`Ng82T-_T@2)9qhb`cjB!>q6zCofGWV|kUMvR(VA zns|}k$!y~EQS6Kr*Jk0ORlV4*=nx~dpK_I<A;uNCQS)Sh(88oVE%Pl8G#(S9hEo_z z_&})O;n+JH)At9Hb`VY;5N}Z?nb#1vGp#pIs%|@<3tniusCf`jh<JP2+nxerh8Z>C zp;54tr(!y*9~N0#V!RFyfh)acreJ)NU>JJz+L;CV$Rb`{SgP;ZZ2Zf)-3&dJ*P=Z% z>qeHejw=R3y5*(q#|9<}SQX!}yG;)@&qM&Hc}Rw5w9sj?hAWZJ5o?TT^ZPex>A!iB zZUq(u?~V>GZ}+-bBpj=B8ERD8{^4cZ;{MkWM+_0m%Rl5DPtTA|-gAdH!}Lf5A%wgk zQCSZC--LWp9m4+qiQ7Nn>fgoi4}kk`{)X_<AV(-`(4e#<tTYr*5)hv>^iVs=5I@Pt z5!jkUwEqgn*dP$MG6+QV-v|EHUBHMCFbx8#Nd*3%q(%n<f&b4C2_jHa9YqDvtEqy* zkHFSq`7is15Y!?^MU_UVX|aI+*KL46ME_?w1re<!i)x{Q$W`V-9BFZ*4yhujwV5$z J)&FzsKLD#ll7IjJ delta 5953 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 diff --git a/website/concurrency.html b/website/concurrency.html new file mode 100644 index 0000000..65094a3 --- /dev/null +++ b/website/concurrency.html @@ -0,0 +1,419 @@ +<!DOCTYPE html> +<html> + <head> + <title>Parallele Programmierung</title> + <meta charset="utf-8"> + <link href="css/hdm.css" rel="stylesheet"></link> + <style> + /* yanone-kaffeesatz-regular - latin */ + @font-face { + font-family: 'Yanone Kaffeesatz'; + font-style: normal; + font-weight: 400; + src: url('fonts/yanone-kaffeesatz-v22-latin-regular.eot'); /* IE9 Compat Modes */ + src: local(''), + url('fonts/yanone-kaffeesatz-v22-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('fonts/yanone-kaffeesatz-v22-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('fonts/yanone-kaffeesatz-v22-latin-regular.woff') format('woff'), /* Modern Browsers */ + url('fonts/yanone-kaffeesatz-v22-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ + url('fonts/yanone-kaffeesatz-v22-latin-regular.svg#YanoneKaffeesatz') format('svg'); /* Legacy iOS */ + } + /* noto-serif-regular - latin */ + @font-face { + font-family: 'Noto Serif'; + font-style: normal; + font-weight: 400; + src: url('fonts/noto-serif-v20-latin-regular.eot'); /* IE9 Compat Modes */ + src: local(''), + url('fonts/noto-serif-v20-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('fonts/noto-serif-v20-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('fonts/noto-serif-v20-latin-regular.woff') format('woff'), /* Modern Browsers */ + url('fonts/noto-serif-v20-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ + url('fonts/noto-serif-v20-latin-regular.svg#NotoSerif') format('svg'); /* Legacy iOS */ + } + /* ubuntu-mono-regular - latin */ + @font-face { + font-family: 'Ubuntu Mono'; + font-style: normal; + font-weight: 400; + src: url('fonts/ubuntu-mono-v14-latin-regular.eot'); /* IE9 Compat Modes */ + src: local(''), + url('fonts/ubuntu-mono-v14-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('fonts/ubuntu-mono-v14-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('fonts/ubuntu-mono-v14-latin-regular.woff') format('woff'), /* Modern Browsers */ + url('fonts/ubuntu-mono-v14-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ + url('fonts/ubuntu-mono-v14-latin-regular.svg#UbuntuMono') format('svg'); /* Legacy iOS */ + } + /* ubuntu-mono-italic - latin */ + @font-face { + font-family: 'Ubuntu Mono'; + font-style: italic; + font-weight: 400; + src: url('fonts/ubuntu-mono-v14-latin-italic.eot'); /* IE9 Compat Modes */ + src: local(''), + url('fonts/ubuntu-mono-v14-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('fonts/ubuntu-mono-v14-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */ + url('fonts/ubuntu-mono-v14-latin-italic.woff') format('woff'), /* Modern Browsers */ + url('fonts/ubuntu-mono-v14-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */ + url('fonts/ubuntu-mono-v14-latin-italic.svg#UbuntuMono') format('svg'); /* Legacy iOS */ + } + /* ubuntu-mono-700 - latin */ + @font-face { + font-family: 'Ubuntu Mono'; + font-style: normal; + font-weight: 700; + src: url('fonts/ubuntu-mono-v14-latin-700.eot'); /* IE9 Compat Modes */ + src: local(''), + url('fonts/ubuntu-mono-v14-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('fonts/ubuntu-mono-v14-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ + url('fonts/ubuntu-mono-v14-latin-700.woff') format('woff'), /* Modern Browsers */ + url('fonts/ubuntu-mono-v14-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */ + url('fonts/ubuntu-mono-v14-latin-700.svg#UbuntuMono') format('svg'); /* Legacy iOS */ + } + + body { font-family: 'Noto Serif'; } + h1, h2, h3 { + font-family: 'Yanone Kaffeesatz'; + font-weight: normal; + } + .remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; } + </style> + </head> + <body> + <textarea id="source"> + +class: center, middle, first + +# Software-Entwicklung 3 + +## Parallele Programmierung + +--- + +# Agenda + +1. Recap +2. Parallele Programmierung +3. https://www.vogella.com/tutorials/JavaConcurrency/article.html +4. https://www.baeldung.com/java-concurrency + +* Warum wird parallele Programmierung benötigt? +* Prozesse <-> Threads +* Amdahls Law +* Wie funktioniert parallele Programmierung? +* Wie funktioniert parallele Programmierung in Java? + * `Thread` + * `Runnable` + * `wait()`, `join()`, `notify()` + * `volatile` + * `Executor`-Framework +* Probleme bei paralleler Programmierung + * `shared state` + * Lost Update/Race conditions/Dirty Read + * Dead Lock -> https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html +* Möglichkeiten, Concurrency-Probleme in Java zu beheben + * Locks/Monitor-Objekte/`synchronized` + * `Atmoic`-Klassen + * `Concurrent`-Collections + * `Future`s, `Callable` + * Funktionale Programmierung -> Java Streams + * Was bedeutet funktionale Programmierung? +* Was ist Skalierung? Horizontale Skalierung <-> vertikale Skalierung + +--- + +# Recap: Was haben wir in der letzten Vorlesung besprochen? + +* SQL +* NoSQL + +> Blick ins [Repository](https://gitlab.mi.hdm-stuttgart.de/jordine/se3sose2022projekt) + +--- +class: center, middle +# Welche Formen der Kommunikationsbeziehungen kennen Sie in der (objektorientierten) Programmierung? + +--- +# Kommunikationsbeziehungen + +Beispiele: + +* lokale Schnittstellen + * API (lokal) +* Remote Schnittstellen + * RPC + * RESTful-APIs + * SOAP-APIs + * Messaging-APIs/Event-based APIs + +--- +# Gruppierung der Schnittstellenarten + +## Synchrone Schnittstellen + +## Asynchrone Schnittstellen + +--- +# Definition synchrone Schnittstelle + +> Der Sender **blockiert** bis vom Empfänger eine Antwort geschickt wurde. + +## Beispiele für synchrone Kommunikation + +* HTTP +* SOAP +* GraphQL +* RPC (z.B. RMI, gRPC) +* REST + +--- + +# Ablauf synchrone Schnittstelle (vereinfacht) + +## Sender +1. Verfassen einer Nachricht +2. Verpacken der Nachricht (_Marshalling_) +3. Absenden der Nachricht +4. _Warten auf Antwort_ +5. Empfangen der Antwort-Nachricht +6. Auspacken der Antwort-Nachricht +7. Verarbeiten der Antwort-Nachricht + +--- +# Ablauf synchrone Schnittstelle (vereinfacht) + +## Empfänger +1. _Warten auf eingehende Nachrichten_ +2. Empfangen der Nachricht +3. Auspacken der Nachricht +4. Verarbeiten der Nachricht +5. Verfassen der Antwort-Nachricht +6. Verpacken der Antwort-Nachricht +7. Verschicken der Antwort-Nachricht + +--- +# Ablauf synchrone Schnittstelle + +**Neu!** + +> Blick ins [Vorlesungs-Repository](https://gitlab.mi.hdm-stuttgart.de/jordine/se3sose2022vorlesung) + +--- +# Was ist REST? + +> REST: Representational State Transfer + +Softwarearchitekturstil + +--- +# Grundprinzipien + +_nach [Wikipedia, 2022](https://de.wikipedia.org/wiki/Representational_State_Transfer)_ + +* Client/Server +* Zustandslosigkeit +* Caching +* Einheitliche Schnittstelle + * Adressierbarkeit von Ressourcen + * Repräsentationen zur Veränderung von Ressourcen + * Selbstbeschreibende Nachrichten + * „Hypermedia as the Engine of Application State“ (HATEOAS) +* Mehrschichtige Systeme +* Code on Demand (optional) +--- +# REST-URIs + +> URI: Uniform Resource Identifier + +Mögliche URI-Muster: +* `<protocol>://<service-name>/<resource-type>/<resource-id>` +* `<resource-type>/<resource-id>` +* `<protocol>://<service-name>/<resource-type>/<resource-id>/<resource-details>` +* `<protocol>://<service-name>/<api-version>/<resource-type>/<resource-id>` + +Beispiele: +* `https://music-mamager.app/songs/112233` +* `https://music-mamager.app/songs/112233/play` +* `https://music-mamager.app/songs/112233/artist` +* `/v3/songs/112233/artist/json` + +--- +# REST im Einsatz + +* Verwendung auf Basis von HTTP/HTTPS +* Nutzen der [HTTP-Verben](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) + * `POST` + * `GET` + * `DELETE` + * `PUT` (entspricht einem Update) + * ... +* Nutzen der HTTP-Status-Codes für Antworten + * [Ãœbersicht der HTTP-Status-Codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) + +--- +# REST-APIs mit OpenAPI 3/Swagger + +* API-Spezifikation +* API-Dokumentation +* API-Generierung +* Testumgebung + +[Petstore Demo Projekt](https://petstore.swagger.io/) + +--- +# REST mit Spring Boot (I) + +* Alternative zu `JAX-WS` +* Maven-Dependency hinzufügen (`spring-boot-starter-web`) + +```xml +<dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <version>2.7.0</version> + <!-- ... --> +</dependency> +``` +* Erstellen einer Klasse, die mit `@RestController` annotiert wird +* Annotieren der Methoden mit geeigneter HTTP-Verb Annotation (z.B. `@GetMapping`) und zuweisen des Resource-Pfads (z.B. `/songs`) +* Mapping der URL-Parameter zu Java-Parametern mit der `@RequestParam(value = "name", defaultValue = "Song 2")` + +--- +# REST mit Spring Boot (II) + +Start der Anwendung: + +```java +@SpringBootApplication +public class RestServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(RestServiceApplication.class, args); + } + +} +``` +--- +# REST mit Spring Boot (III) + +_Quelle: ["Building a RESTful Web Service", spring.io, 2022](https://spring.io/guides/gs/rest-service/)_ + +```java +package com.example.restservice; + +public class Greeting { + + private final long id; + private final String content; + + public Greeting(long id, String content) { + this.id = id; + this.content = content; + } + + public long getId() { + return id; + } + + public String getContent() { + return content; + } +} +``` + +--- +# REST mit Spring Boot (IV) + +```java +package com.example.restservice; + +import java.util.concurrent.atomic.AtomicLong; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class GreetingController { + + private static final String template = "Hello, %s!"; + private final AtomicLong counter = new AtomicLong(); + + @GetMapping("/greeting") + public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { + return new Greeting(counter.incrementAndGet(), String.format(template, name)); + } +} +``` +--- +# REST-Demo im Projekt + +> Blick ins [Repository](https://gitlab.mi.hdm-stuttgart.de/jordine/se3sose2022projekt) + +--- +class: center, middle + +# Asynchrone Kommunikationsbeziehungen + +--- +# Definition asynchrone Schnittstelle + +> Sender **blockiert nicht** bis vom Empfänger eine Antwort geschickt wurde. + +## Beispiele asynchrone Kommunikation + +* Chats +* E-Mail +* Message Broker/Queues (implementierungsabhängig) + +--- +# Message Broker + +_nach: "Learn Microservices with Spring Boot", Moisés Macero GarcÃa, apress, 2020, S. 216_ + +<img alt="Schematische Darstellung Message Broker" src="img/schnittstellen/message_broker.png" width="100%"/> + +--- +# Ablauf asynchrone Schnittstelle (vereinfacht) + +## Sender +1. Verfassen einer Nachricht +2. Verpacken der Nachricht (_Marshalling_) +3. Absenden der Nachricht +4. _Weiter im Programmfluss_ +5. _Ggf. Empfangen der Antwort-Nachricht (nach Zeitraum X)_ +6. _Ggf. Auspacken der Antwort-Nachricht_ +7. _Ggf. Verarbeiten der Antwort-Nachricht_ + +--- +# Ablauf asynchrone Schnittstelle (vereinfacht) + +## Empfänger +1. _Warten auf eingehende Nachrichten_ +2. Empfangen der Nachricht +3. Auspacken der Nachricht +4. Verarbeiten der Nachricht +5. _Ggf. Verfassen der Antwort-Nachricht_ +6. _Ggf. Verpacken der Antwort-Nachricht_ +7. _Ggf. Verschicken der Antwort-Nachricht_ + +--- +# Backup Themen: Software im Betrieb + +* Eventual consistency +* Monitoring +* Logging +* Security +* Patching +* Releaseplanung +* Downtimes +* Dokumentation/Schulungen + + </textarea> + <script src="js/remark.min.js"> + </script> + <script> + var slideshow = remark.create(); + </script> + </body> +</html> \ No newline at end of file -- GitLab