From 3a38e2be6bb9bb326f2d801fb12ff7b439ea56da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Fri, 24 May 2024 20:56:06 +0300 Subject: [PATCH] refactor: remove tls and thread pool --- pom.xml | 2 - thread-local-storage/README.md | 219 ------------------ .../etc/thread-local-storage.urm.png | Bin 43613 -> 0 bytes .../etc/thread-local-storage.urm.puml | 29 --- thread-local-storage/pom.xml | 46 ---- .../iluwatar/AbstractThreadLocalExample.java | 70 ------ .../java/com/iluwatar/WithThreadLocal.java | 55 ----- .../java/com/iluwatar/WithoutThreadLocal.java | 48 ---- .../src/test/java/ThreadLocalTest.java | 91 -------- thread-pool/README.md | 219 ------------------ thread-pool/etc/thread-pool.urm.puml | 37 --- thread-pool/etc/thread_pool_urm.png | Bin 30120 -> 0 bytes thread-pool/pom.xml | 67 ------ .../java/com/iluwatar/threadpool/App.java | 92 -------- .../iluwatar/threadpool/CoffeeMakingTask.java | 42 ---- .../threadpool/PotatoPeelingTask.java | 42 ---- .../java/com/iluwatar/threadpool/Task.java | 51 ---- .../java/com/iluwatar/threadpool/Worker.java | 51 ---- .../java/com/iluwatar/threadpool/AppTest.java | 41 ---- .../threadpool/CoffeeMakingTaskTest.java | 40 ---- .../threadpool/PotatoPeelingTaskTest.java | 40 ---- .../com/iluwatar/threadpool/TaskTest.java | 145 ------------ .../com/iluwatar/threadpool/WorkerTest.java | 53 ----- 23 files changed, 1480 deletions(-) delete mode 100644 thread-local-storage/README.md delete mode 100644 thread-local-storage/etc/thread-local-storage.urm.png delete mode 100644 thread-local-storage/etc/thread-local-storage.urm.puml delete mode 100644 thread-local-storage/pom.xml delete mode 100644 thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java delete mode 100644 thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java delete mode 100644 thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java delete mode 100644 thread-local-storage/src/test/java/ThreadLocalTest.java delete mode 100644 thread-pool/README.md delete mode 100644 thread-pool/etc/thread-pool.urm.puml delete mode 100644 thread-pool/etc/thread_pool_urm.png delete mode 100644 thread-pool/pom.xml delete mode 100644 thread-pool/src/main/java/com/iluwatar/threadpool/App.java delete mode 100644 thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java delete mode 100644 thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java delete mode 100644 thread-pool/src/main/java/com/iluwatar/threadpool/Task.java delete mode 100644 thread-pool/src/main/java/com/iluwatar/threadpool/Worker.java delete mode 100644 thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java delete mode 100644 thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java delete mode 100644 thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java delete mode 100644 thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java delete mode 100644 thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java diff --git a/pom.xml b/pom.xml index a22f0ac9c..2b24a7806 100644 --- a/pom.xml +++ b/pom.xml @@ -108,7 +108,6 @@ double-dispatch multiton resource-acquisition-is-initialization - thread-pool twin private-class-data object-pool @@ -205,7 +204,6 @@ identity-map component context-object - thread-local-storage optimistic-offline-lock curiously-recurring-template-pattern log-aggregation diff --git a/thread-local-storage/README.md b/thread-local-storage/README.md deleted file mode 100644 index 7d5d4b902..000000000 --- a/thread-local-storage/README.md +++ /dev/null @@ -1,219 +0,0 @@ ---- -title: Thread Local Storage -category: Concurrency -language: en -tag: - - Isolation - - Memory management - - Thread management ---- - -## Also known as - -* TLS -* Thread-Specific Storage - -## Intent - -To provide each thread with its own isolated instance of a variable, avoiding shared access and synchronization issues. - -## Explanation - -Real-world example - -> Imagine a busy restaurant where each waiter has their own personal notepad to take orders from customers. Each waiter's notepad is separate and used only by that specific waiter. This setup ensures that no waiter interferes with another's orders, avoiding confusion and mistakes. In this analogy, the restaurant represents the application, the waiters represent the threads, and the notepads represent thread-local storage, where each thread maintains its own isolated data. - -In plain words - -> Thread Local Storage provides each thread with its own isolated instance of a variable, eliminating the need for synchronization and avoiding shared access issues. - -Wikipedia says - -> In computer programming, thread-local storage (TLS) is a memory management method that uses static or global memory local to a thread. The concept allows storage of data that appears to be global in a system with separate threads. - -**Programmatic Example** - -Consider a scenario where threads need to maintain their own state without interfering with each other. We'll demonstrate this with two implementations: - -1. With ThreadLocal: Each thread has its own isolated instance of a variable. -2. Without ThreadLocal: Threads share a single instance of a variable, leading to potential conflicts. - -We start walking through the code from the base class. - -**AbstractThreadLocalExample** - -* Implements `Runnable` and includes `run` method which pauses the thread, prints the current value, and then changes it. -* `getter` and `setter` methods are abstract and will be implemented by subclasses. - - -```java -public abstract class AbstractThreadLocalExample implements Runnable { - - private static final Random RND = new Random(); - - @Override - public void run() { - try { - // Pause thread for a random duration - Thread.sleep(RND.nextInt(1000)); - // Print the current value before changing it - System.out.println(getCurrentThreadName() + ", before value changing: " + getter().get()); - // Change the value - setter().accept(RND.nextInt(1000)); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - protected abstract Consumer setter(); - protected abstract Supplier getter(); - - private String getCurrentThreadName() { - return Thread.currentThread().getName(); - } -} -``` - -**WithThreadLocal** - -* Uses `ThreadLocal` to ensure each thread has its own instance of `value`. - - -```java -public class WithThreadLocal extends AbstractThreadLocalExample { - - private ThreadLocal value = ThreadLocal.withInitial(() -> 0); - - @Override - protected Consumer setter() { - return value::set; - } - - @Override - protected Supplier getter() { - return value::get; - } -} -``` - -**WithoutThreadLocal** - -* Uses a shared `value` among all threads, demonstrating potential interference between threads. - - -```java -public class WithoutThreadLocal extends AbstractThreadLocalExample { - - private Integer value = 0; - - @Override - protected Consumer setter() { - return integer -> value = integer; - } - - @Override - protected Supplier getter() { - return () -> value; - } -} -``` - -**ThreadLocalTest** - -* Tests both implementations by creating two threads for each and verifying their behavior. - -```java -public class ThreadLocalTest { - - @Test - public void withoutThreadLocal() throws InterruptedException { - ExecutorService executor = Executors.newFixedThreadPool(2); - for (int i = 0; i < 2; i++) { - executor.submit(new WithoutThreadLocal()); - } - executor.shutdown(); - executor.awaitTermination(1, TimeUnit.SECONDS); - } - - @Test - public void withThreadLocal() throws InterruptedException { - ExecutorService executor = Executors.newFixedThreadPool(2); - for (int i = 0; i < 2; i++) { - executor.submit(new WithThreadLocal()); - } - executor.shutdown(); - executor.awaitTermination(1, TimeUnit.SECONDS); - } -} -``` - -The output of test named withThreadLocal: - -``` -pool-2-thread-2, before value changing: 1234567890 -pool-2-thread-1, before value changing: 1234567890 -``` - -And the output of withoutThreadLocal: - -``` -pool-1-thread-2, before value changing: 1234567890 -pool-1-thread-1, before value changing: 848843054 -``` - -Where 1234567890 is our initial value. We see that in test withoutThreadLocal thread 2 got out from LockSupport#parkNanos earlier than the first and change value in shared variable. - -This example demonstrates how `ThreadLocal` variables provide isolated storage for each thread, preventing interference from other threads, whereas shared variables can lead to unexpected changes and thread interference. - -## Class diagram - -![Thread Local Storage](./etc/thread-local-storage.urm.png "Thread Local Storage") - -## Applicability - -* Use when you need to avoid synchronization for performance reasons by providing each thread with its own instance of a variable. -* Useful in scenarios where threads need to maintain state information independently of other threads. -* Suitable for managing per-thread lifecycle states in web servers or handling thread-local configuration in multithreaded applications. - -## Tutorials - -* [An Introduction to ThreadLocal in Java - Baeldung](https://www.baeldung.com/java-threadlocal) - -## Known uses - -* Java ThreadLocal class, commonly used to manage user sessions in web applications. -* Database connections, where each thread gets its own connection instance. -* Locale settings in internationalized applications to ensure thread-specific locale information. -* In java.lang.Thread during thread initialization -* In java.net.URL to prevent recursive provider lookups -* In org.junit.runners.BlockJUnit4ClassRunner to contain current rule -* In org.springframework:spring-web to store request context -* In org.apache.util.net.Nio2Endpoint to allow detecting if a completion handler completes inline -* In io.micrometer to avoid problems with not thread-safe NumberFormat - -## Consequences - -Benefits: - -* Eliminates the need for synchronization, which can improve performance in multithreaded environments. -* Simplifies design by avoiding complex synchronization mechanisms. -* Each thread has its own dedicated storage, reducing contention. - -Trade-offs: - -* Increased memory usage due to multiple instances of the variable. -* Potential for memory leaks if thread-local variables are not properly managed, especially in long-running applications or thread pools. -* Debugging can be more complex due to thread-specific storage behavior. - -## Related Patterns - -* [Singleton](https://java-design-patterns.com/patterns/singleton/): Both patterns ensure a unique instance of a variable, but Thread Local Storage ensures one per thread rather than one per application. -* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): While Flyweight shares instances to minimize memory usage, Thread Local Storage creates separate instances for each thread. - -## Credits - -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) -* [Effective Java](https://amzn.to/4cGk2Jz) -* [Java Concurrency in Practice](https://amzn.to/4aRMruW) -* [A Deep dive into (implicit) Thread Local Storage - Chao-tic](https://chao-tic.github.io/blog/2018/12/25/tls) -* [ELF Handling for Thread-Local Storage - Red Hat Inc.](https://uclibc.org/docs/tls.pdf) diff --git a/thread-local-storage/etc/thread-local-storage.urm.png b/thread-local-storage/etc/thread-local-storage.urm.png deleted file mode 100644 index f090fbd4a7a52dd901f6621837e10bb7c448fb40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43613 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≠|8K@q3nR`y(?7~oFza{@#U{0BO?#q7=?HKs5_Y<)ZH_<6VKC!l$9_MpuBI%$Ia8Hn95OmgLo#AkL- zGqFpQCi~Y0?AnJW*a!dFaO0*txvR%?Jjnwn)k!JV$PP9Nsi2*;M5MiyWJ{Nu3nDse zy*ZfJ3!z3)xokDF)DW>!7J!Xzb#))ryPrw8cXxHETD4is^;KA@qdqdR-)VGG@6sIk zG-6n}8&75H3lbNALcS7)-n+fcfo_aLM=KRQopvD^pJ(aM%v@B{2OBnYy3XotYq21i zWeo&zyDtRioHt_@4P9-=XZ`;P-d|$;i|)f9-gekf#Q{E<8AG!xqhGZlJGO{CNKbjG zMX}oWS;f4)OUtrBc|`$Ke_M6$+ZS2yeECpwzwX7<2FYy*kzNj49rM!u9`-_&YB9 zfn;ivp!!kcg-SNLeMwH;VTT*kN68)8d`u7si;T==3yA&7V>*3rI^%<@6nW5ku|~|Z zXHxd#l^}{j1o&t|gB(EDkUetLJ!4_64K;zDGiEEKiTe*~hR-imO}_C!rWyD8;gYC~ zl$huSp9>*l&P`|pu~rI!={qltG7svciE*nvW7gu&AU!Q^%q?f2f5N3>EN}^c|wlLauc43k^5+1SVlm|l^v{`;fhibW2mZNq? zMhsp31iZS|LoedJl@B|Uk-omZ+7Ba!JI+Xaf`ZL|(n5c;fqs5P+@*IJeOaJ^y4+S9 zdP2q1(%t9wmZ)Erl`+M$GmZc2?WtdRT&$f)AW|HUd7d=oQsVS_xI z+V}UQJA4%~G5PD)`xK?PyUi`xhC7At%e?A2fN#V6FYULUj5v{b^KoP-K_4=au(d z7yF%^T6YzXXr2)m2v-ED8Q>PTE)0H0LIzd1obcik0!*aRiFNb}e!#(>sV9%0X37Ch zwTaL<}~}{uUikyWg$N^u87W_H!VHdIJcFIBg@wua01eXB)u9( z*0jUE=`hkeaB^Mtr)~AlL97$Nc-+%*w{!}7I*})w`kTh2{Z6ugOFxCe__x9w&|xe= zhpCOu!CaR~B5G!G5*qdMBu1ux)N05ykSw=)Mcj_ajQ?`=5tcN^J06bb;-V!C_t-1X zQ`IBB#G>ra?7siHr3o5TUYE5cd2dIMDVf;06=_L%CRm>vct{y*jt9}$TBAJ#icS6_ z`fle^<{K1cy-&)|@$jHah6_0y(Up`ag%?}2JC(HmH3L{np(%7x0*)*f_BGo{_^rMr zq*74ay~_NM?M5UJl{(vT`5II$R8KQZu?eOnlG(3H(f8imRC+|8=RNsyy<4%Inrrbh{ z7mK2CWTl$}ESOQ|nzTn@CL+0wJLD-UlpPd59wMjLt(Kn0mG-8th|zCSQoJzhiEcj~ zu_hB~%bndIn)fa>g4$_mkjL$}PX8;~gS4Jr6CBpvu+p1OPHRAsZl81HhQ!kD4zlSk zRm{e@ogPGHI;!vUdiR$ddTHcRwd+i8!z$H6rGotgM;Adw{0m zmW}EH16L)&*bGT13h1DmfLr71=7>q6pZiU&oJv};w|Q}xh}jxoyNg-Bi|mkJkWe94 z^fQLier$GtL;5F`qC&t+w!A`RYtwG^lc#)|qnIhDN=dIQwcH{OH0qXbcafCgW1VJ* zsC;r#?>S*JU;2hn!O4jZIM0`;Y~=%l!tE+4{a z*D8A4lFqL|kr@#UfBx-y@tpzS#oL?oRKLhTV1;yE2%mnuGa!5;O%W$C@^Z|r;QIW) zj@pn%>j}y?=hb4nHSJ2{kY}94=xN1IpAIr=^sNWcK`j@*EiP(maU#6Bx;mAjWok-V zNT@e8dK}$3rFZJ_N2qD-NDxrI1l6@YlV4u-P(6)VT=v>P0k!(r?UF~5cRisf=#bb- zz0T%f6x)H1J$mG&N7bsaLv9UfAY3UwwJ^Eb=#e-oyCP1LfL)_oUQvWKlO{PiSy53D z3M-*{2lyp)&@L+5C<2O#bai#N2;!1iJ;wJV;#E8Z$IdPQzu*lem6oAqb7y~dj!k%Y z84fKM1!~)2-97)u(++~n(>ztgdw;WES?5a-oJ=oQVJK#p>yYMe`5&C5|)A=t>G)pL8!bC^QkZOaTL3gr$pu%_>Z;KeXl}MC%PrE?$nCz)!0se?Cd-)EE6y;XB zy9MEKBSw!ppmC$9Tl14?LHAgGdbH3DC^g=JYJqZ_V*bdOyO(isabb-yh0#~+4?lGD z>hz>s4jYa&cqVKUvxbh{LB)Ri+&wYDYh>lAML|gkRIUns&#!N-1GJ2~IHO{)_`4`` z&#ltTpNem<1a#7PNYyN7Y#QH;ys~Oja4R641_AU94_8&Uy(?Y6m)dP*<@KPiD)c1C%woCIq(e&(LS7=`u zZEVM&q+g|jZ=ie{?$!B}SK*#K9Q?xg$*^oKPevbiDWHumbje+~dO}7LW^_~_BOGS{=cxQxV5V6IpQfesU+v(v*QrGMLz(7es7qo_apGoN?YIpbw zR{{h;d3$8;42`QGqiq{UwP|%~oNn!U0<6oQb9xOswc{~#+BmFrdxZo|{AYSki!lU! zw?AnB$35RZxW?6^<)ufDbGv{_4->i$6m|4ahlB9M-^1~AOqMo&%>rq_7hR3r!%HXGDo)yc;9g5+BlMRA z!ZSRfOF>`Oc~MU;vqQl{^?AZ%n5cn$xk9cJBG~$tZBge zwoQ)kgvG1qysjxdjy5|p(6)BC(#5h62nsuSA|K3CC#8E&L#POjM~Dm!7!P0b>YlSW zt*Oq}$9p#G$33+}r)xL}_R(+}vGMSo8Qzn)mkisIYRf>ctxqegu0nj1L1lLzWT$gj z|N8nK1WH~=>s3^UAM-!10d}zh-36UJZ7=%hRr5{^rqtEdH8vhW4Z5CKnk={TdEmq! zj%OZSDE`se0~7BB8{texnXh9fWOf=hYMd2Bh>wk9J74{#_%H5I?&_gfgL)#wq1S43mgR^`BzD-Q8WV(bb9Ss1AVhK&@zO z<=0tw^765;$Q0HY&4%J4tg*;b9m8YCbH=4#cOj4=vSsU4NQN9T*UZWzw%l-_ttCLP^XDoDm8 z=Jbw-@B08WQJLf@JSCIbmMN5w!>%lN%rzS%f{Fsz7gx%?>o22H-Z)(H>JyIt8L#4) zc)F{T;@#l2?o)&HUhZ20jX-KalT9&{v|@Ealqw0zb~v0fS1rw;NUsdovAeET+L7D{ zLaa-Qv_%G66xTq5BjYYbi?go|{k)+L6np zmbw5mX*vtV*(KTjAKjx28VD)_tbZRwuccRgg>DN7p)M?Lm!{|DIMeUZXfLpj_Vh#v zo}4pt#+*))$e#+f9z$EFC~YfoiefGzm7^27goqZ$D!BL>U6*Nm^k!Defs*ci|2`-@ zaH&-A98CQIcIsR(b+ENVkxJ+HZ#>rHJWOiz*W3Mup8!sPyn5gf1T7%;I4Y8bZ8QT; zk!*XOB(hYaV_e&Bpt6Hi#AdHNnG+ci*&qJPVJ+eJcePXGSR$_hdwN_O+yZ?@Ah@i= z9R!nKmgN9B-}mPi9KOD#?tOSw1W=53Htgp-m&Vf+H(kr;F9uFI>R4fVcU19cH#L>? ztkoJSblyN`LS=SMdY(>p1!s)Bwq6oj$MZmeqzc2pRB_iQD}k!EW8sGr@BWLHE|Fw; zcU~x~?NZ>(v=MSpDBvmSHxY<`dac&4vz_9do&4d>txGXV@w{b=eSa$~7sr+-$HIt3kp&rbYh%qyh%@pKhz@@*SuOxy|B7vH)LbbO! zGBV)Dj~{S2pM-{fL02WjTx&;m_844*)<-`-zn4EDHFh!5dB)~sZ8;KMu7d`ep`oFn z@#nbL5lbbWH9{EXp!OGWVPFuH;T0TjQhN2gYUtcpG;B0Hu$N*GMa`DIq+e>>{ z*7i0uSJu>s-VND85!nc2-L~3Y{`yrm$ilcNa`FxtDpq|hnS&epw;7~Rjz3rTAA*2;v65`2Rz^W;EIkhIzXYPgWtSSSO45b!4yHKJKVzCWH!sDWfb(1t*Pr3Y>H=I*#}4Z39_3HcFDFj(6n-W1Q?m z0>ra`KI{nMVy$MRZBOpezK4#fbsS3Z;|WGD6qz*MzBIf4+zpCNTVM&K@WUfJo#JO=jVo~0;IVP~8_CxXPy{aaIB{?sA?NVeD6BU}?y)C2nI4GJ_l zE(xs(Z3+fX09F21xO$b0PrFz*H`L%#lv#9XC4P5^Pa>AiiR>g+030-j4WIH_m2^98 zz&8je5SER6EXAe4(x>ff$>vF~Mu# z;J0Dk9cSKln&b-4`+yD{rnTiT0WxjeSorYy80)n7=qIX4LJt!-=wsc34*sKL-NmrJ zG#+g;#?(4j#X_=YvAZL0Vq(Ij6?|Gz;_pQ6%b6Zl=%ilph%sO>|ANQ4QDM8F;GZ*2 zt=VAzVSn;`@`dQmG>JqVY!j9Fhj0y(X{?}E+ys1JzW3J3SW!w&>9Oc7{_%fuf*aDw z&z`BKe1{@2F%}{!M8YWm1lCARx6D2b+EXMUU}&zct_Ra0%$4P;3b|&QQmXT4*MzUZ zP5V~G8CAtvd65{*M45T(ww~J9Wfyjt3vuozyJ95Ue(SfHJJ}-VR%vL*r6K%fy$51S z!ls{nwu^J!p0o9L5Dn}1=yXA}$ZArTwZM6sB4oV})P8~kf)lj?47p8+Gxf@eQI$v* zB5fua8t!OA@I}BS0H6&s39&9vL8H;2xd8v;1Jzo+y}fXJD>4=MCQd}$s`P}Ot~!tP zTu&Wap3O7XzQa#I;vm$@v-?!@G%^XR@7;!DunD0;SYH-*l&4eUa_K=rOAJj887mXt!I^p`vIUQV0A1g=)m)-$ZbP+ z^8$E?wB7A(h+>c8Z7ngNSrexP-DGtpzXvbkx<huTXjxbjg<0ye9JYpmk6IkI%Cy2S?9DUnv)+9J>4uD z5@O=GD2xI9`Q(F!o8RPui8E0)_E1EBb>i?x-D-I>aDlFoPoB_8d5e`Rblm+SP3zIK?Ba?=fEtA>;R-T3N6+u2zPqk32K;gvF*Ta}meb(1zKs)? z3SfKq)|JK23`{3=?_4mdX&T|y8y!COXX3KiYCbu!ClvmmV}ffm+`}ou_srnv2+{mg zP2{j7NduDSOMjWX5g|7egKpNFx-PM$!FmBI*8aFc=E4TkzkjM+1_wG1LXWEuL3rH{ zX}y$hFTcAt-uc6LeTiF32Jp{KF_WPaJzOYt;lOB;M?^Qjs|1}SWdGUlRRbsKhu(6W&Pr6f=+8a!o);O3s0 zx4_nJBv^;RGzbsgsH@4vEt^yx+p=XEDIiFT&INESe662t<+o6Nb z3R=vdiR<=*>63K6+cKGEhxG$%7s03A?wTSZy0oir9s;kcn_EOxyaoRKF5#ApcE|rq0PbEVZ5{gmTB* za?1Y=((lk)eCQCO&^AISg~9&Av#T`R6PlytGSq57nazaN)YO19d<54Acq7g|k(+>R z3=%jt4m_C#mZ-(P>>GA={LF}8Y#}JNdyl1YA4=d@Sy|WTd*<<)M6tvhBm6igV_;wa z=+A9sv>D(3Y9~fS5N;^I_5}JbVmu*`gik|5EOZ}3GWr#-iNF^s;uaRH2_jUvXA0t> z`T~lA01W@UL1O&3;ql8TRId(}m>fYl08FkK6wsKMm|VPNS&AK?UR6?rACnpDk{yWgkGjOD&&!0aJEbB*ngd;b|Q=_@6^o0jhXA#5i{&sAh zJfRq4P-$HT_4ZN-{{YAR?fWJsQ=q>7!a)$8`1dqXqk7MwsVP4wLG(r->gEAHVtKhL z+q2Awj11F1os|B#?;#{C4HtWX5)OkguEV_#K=Igj#PN6|{BZ*{?y%V5qwomnpkkJo znD`B-@63p%+sR_i==tunUjybDrsn^KYQtyt{t2zG*dB9uw` zCwMPfQO7UPW&vHuTkpU*?jIaLIR&)Vay$NQ}bt!69shiKjsU~i2nDEkDw*{MM=qOqKPjwWtUzC z6b*Tt89)(O3ID%6rT)0xFw(*PK7hd%_!J|^DO^2LP(2AB`tt8Xk9t5|AAjTF4DG52 z#H%l0t$^wbOHFa$Q!@WnLbe&A;?ZV_-@&g|W>|^Bb@0(pl$R)YG9%-^AA%PB%{h!i zB!gHC*p)F981~oPH#g!r5p?En*DV!|@8QloL`>-Sfz2&?4^(3EX zbpPycVf2-~A>=3^^}mNjI0sb{*5j3npv=F6%@O{$IYKc>N#~fD4#AY6f0@4t${)SG zy}O@Dg3)rT{QFe&CFtJS_<11+780s>-VV_~O)i2uE#CM3OsP<#kgfbfpQ zmhVEz;c|>wWgKqre%Cy(K5W&0KX!8+`eNZ)7wC%$ zB|}6zKuGxV?VI!Az6Y#U=D*_;lm<6X#P&uH!`(J(%`irP*7Y!e$Z&KC`TjK^s{@W7 z16zW2aQNduyz~()`%DbP_wDU%qt`CrO>Wlw>%89fTTpoo*)nvSLDP6r7qr}t0+9-(7O=vE zlK&oh6#eYk=Tft`a34xItOOI*heSlcVj({KZJi#2$`cUuU89(#yYUfikJY7i*somK zpMDzxdtjRSr-8bu0&0=q7Lq?sqjCrA)7!_#Z+jXa+TMlC{xMIl15dFMMVMiv&FN$57!2l*PjNka_s`%=;(76&ULg zBA-JeBjN2VxS(1P_V|nmV&mWY_s-0x`;n56q=`5%!9Zc)e0TTure3>-!7VH0e-8~J zqZJByph+oYp4C4Z&T(&J>sb?T?7bMge@z(S^aUyp!0mIXKbyhJa9oFB-F|%90n5PN z07Cw6A;n^2Vr28aK+}&mg6a+s5_II`zrgvy-uV;!YkIGM{wQAsg|=h+SAp8i_3Ifj z8Ji!Mpau3`Z$>l3XfS0Nynml}ejE|;y)U~J?i^^3A8676F&=vPUNVS^OCN5VL-7o# zaxL*Db#7fFBfp^^%18_$ga4l|vqmk#6(L4OspUdqVq;TNjUa8Nd#^n%XP`pBH8CU5 z9RSUw4Zl9nLxO1*1Rm3FAh+w$Nuu(RlZYArziTaXVH=_^6ztFSr1zlV`XlJHc7p2` zfQ<+a4n~&{F+78(#P;rkZ-QboCNaLfyM`(%op7hb_6vl6>R)exXk&!6fp9eeJe9=i z8#$}*U@|^;I!;a0-2VoxZa=FYzFmW8g}w4;Be4svMo9hq6}tIi8}CX=0X5eA*Ez9_ zcyA8rLM0?Pt_e78=g)$lz;usOSNWS)fS-ZM@F}+$!I2)zxQe&`P8{L!&#$q#!zqPV zhU+r!C$Bh6zP<$u3SRS;J^{m7H_T(coZliG9Pg*5j^N6nx9YTrXukhgK{L2Y46xzR z(M|X&8%smBQ19x^<0w-zp~3*m=kYB^rDMatT2R0cs9Bdhi^Qc2337&qh z{vKR3(UFLYf6WJ@s0tJoTm^D(s-X+a?fkz7b$$gnBkTgV6{yc2;sz6K zkz}5eAt@@e29cpMmLhFbW-7`oO8leZt1;(n|CBB!B20kdtwNg?&!;v$CzVPL<=)=E1l&Smy8fN5fdmEdn1Tr%3x<3c+sd)Th%1dEk zL{psmYbMeFYo6flAi&-;t^5Xbyd+Mxo$KE}oyKR+1IVlaP6n)>R>PR&JoqO-G03l* zUXYP3)&Kdi6$5}Z=wqar?M>Np&Jm4OUI~#s|5xzk5cm6Wc4ap;FwI=;uu1_L+oeBG(uoYV1PbJ`>qH{{ z^(TQ!1Qg<y$+aoz@Apwrt)L{%l_lgFKSmS>=GBqH*PWPt#BD*U3X(I2D4b6{rQ|^ zpN0{~%@`ONkBW)Aw(g8)0BaJ!rVRf8RSQd&UIE zPJ_gw2REl$>c93?Cu7ne{e}iRxB+~Tb1WNfaG1AErcNl>Z7VT3rEeSl<);kI`w06} zd%4L-KCgLaC=R7|@35K3=5tKYSsn~5lx#4ad^Id|Wy!{^)bOtRyyz{AlB5#h$zWRXnR;7r~xpB+3CwDhF zQ5Z4(>sgbH_u?-2q*gfHHe|m*o4O}s<#^sGdi3ZBampLMb!@2J0K`ASU`6XaNG-iz zxP*`behiZsiz}8k)X5J&r)K7jb z^nU3YP6hFV3HF)7`10#OSQ#jm;E@2X$8Y}2F_t6u*D<4noatL#k-NJTuriy8Verz_ zJ>v@>&iB!7Q+$9xh1oru<7jh>JaQk=tq2%Sy@KjDnoT0O5F%KjE07RY(cq5tEOolXiM+gW)7*c_wujSX88F&l_U&==4;9X7 zB|RvJZu^fX2=hBk94J^ra(92o!ZM<=zoq8ru-pAmXN3c6homQJF3-|&*>?OI-0AlA zRQI0hYTq0lI?mnwgM*S7+=p7&QAkiwhwk-0Ej}5?&Qt9-f}*209fX@-`b8A7pT2z2 zwO&S5N34wWH$?d&XWrU_3O+k%3Yo%b8vb4D$;oN9NQcWTqTX(Q3P(b1YEkp*baVh> z2T@0U`}VQ?^zjJzikW`cQqXi}?wW9sF~xX3U<6#fPUz{;9}JkPrEI?DzV(!klA~`S9jvU=X9k;W zA8fk1%*jRrZ`nj%#u{bX`%WRO!N;z_B~=Ck~F^J$^5_>t)%NB6bV?KVZUQU7?GV z|CV=lUoM62zAa$<4jpC^5e5vA>JKfvHizl6T;z*YB6KA1InZS*qF zAvAA&NO15cUf$z3^iOzi3TL?`;?W zX86%7_Q2M1;&f;Lw0;HyHLJhwa~`=@pD1c7p!>5TXM;P#h*Z^=bG-X+e2!NB5q?Zh zWi4myFVBy_-tqGrVxvJN>GY%tUjC6_5KR_$_RP!R5Bsg!MMO29Ydvu_X7q4Mw7IDE z+m_)V&^YeJqty6ccazkAD0J-kXLzH^Ofz;HIO2Br@DJ^qH6!qRJAPZzHvPbXRj0_3 zJo#5`Cmwqe=f|AfQZS`bBGy9Wd32_Wc1Fcx@RvB0mv-|MhGgLT8ObDAa&FwP>FkGK zn|;-k4WfsjFzcD(>o=sw9I**ho=5cC34SH2Y7A5M+EBf~z=`}Bl1I(d2B;KuUR z8v@q^sK{;Q;NVDl2IfP^;l3#pzz!z!S9C3Ru#J7Po@&$;tWm z@_fe=xDg%>{xdwF>C8UQAb~V z>NSrLw?oe19~fyMlDnQCG*5pZ3Yh09G4S*8X=|7dIgzyd81oF1?56^baLc$)5F~o! zI}N^n`(W%hVCOhr;7@MU&EUhC+>nn+ZD8jd2NU(37Xy+zMD(kDmk_;+9V#wNa5(>P zdGbCO(SZM-(Lj0`!FHh!;o z>%>#Us#{WC-*h6;5|g(*dDiczG=%WgC|xfW&QpKg{+7uA-YU zSNt|!&iDohV(f1iPn><^(Tu8p3eCc9CYCm^G zX>IE{1B@dbp|{*8`9Ydp(qFwPMr$freZOwNA1KPUQS9DcOvL7yuViME0sgAE&Y*%x;` zf^!m)T6wtTmcx1djdkYR^jz``YnFDXv^Bq4qVBsuH}KCe4t*tghHvHg@}nPeW#wiQ za%I{YLvm%R+w@iTIKy7$@3=0Z4U^$#$3JF-+I?=4ay&l(-@Cc;cV^Myjc+fKlf^@V zR|tCS{m@6xjUT1NR04tUBWlZrfWxN#QA;+(0T%s@$!z&F0*a)+C96#wWq`?g zN;!wuR{ex&DOs1afVs(r3v)?%Kclw36Uf(umBK{YQ!=J)B-a#;sU00d>3})xGL8oO zjFRsYEkw?r3|Q?iDk41CE%fTG^>s2P8q#kHfzlWai@y~v&|DU&|8M?e+1$rvi{Z1F zg;g#?Red+rp|!inHJ_~?qWkusf&5^6K6iaSm1FykT?|qDT8s&qgPYxIoi!NW5AKq1 zPXI3x86BSn0CM47avQ!NZ~X6{*-fKruor)>{(3)6s5ib~+@8iA zPEFNKV<~Xqa*+5Ye6cp+1JUp;4HD1H%=;qS!<#XW`M19wsZ2qvkT-Gj@YuLvLwA*T zX||f7VPag|QGhZgH)&K7UqD5B4Wk@u^ZtDp1ZFOFZLVLt-81YBQ;HdFpr2l?o8x6gj< zdFxptBt!Bk#`oF5pBrrd5CBZ*rAtyAFr>bAJXz*6i^o+=uzGXSGBl7k@lw)p?j1(A zys!)u7+!fqWF%&dc=+|14qjJGd8;9G{``4>z2RY-)))l#7b2fNfA+?Rxc3Z#-PEuJ zxLK--Q?J#w#h2plv>77gT}u5VZwiXyzD?VPfY^7Lvd&xXfOm>axD&Ommo+hBdhYYl z>l)U+(pr0|Ce<-Nj{b%|0BJ~-fwlS;rAgSfn`+5{=tkC>?Msx!+w{I#t^@--L8#N% z%ef^a7}nmoVz`gwS+_s4TV#&}$v;@nzgqw3!2>qkHE;JW#~oH4aJy|2whj)UN|6F~ zyC3ecvA6&E<43l%s3>j0WYsL4T^9(O8ESy1y`QzY{2C0sXTYweXi{(7c>3)f+t+36 zbaPZPpq+PZN_3QR9J1)WmZPM+g?GEg$4j69b0ja+8J5fCzV@&K@1E{p$5}%Ffd@6sD1s~4YQUrM*@GK zxnSRNidedIUUDsJ1RKD;H@t={P5(-Wv-;JA#r&v1MFH)Pl!@MGyUiRcr*)X)l=YbW zsKl9ry1tIeTbR4TZ*;n3>uGO#*qPINGpT+yP9qXbjWN8Il?1^Fk>izUIN}u39vYpT zRB`MS&}ff?GP;x4hlF>JzCJ_263*|A>AAT=uwMx9F_gWwnvMqvTCg|dka28KaUThC z3KM5~(y%xA624d?leT19Kv$vCtZTS)dQ}O%9Y`CU!Y-wel5wDVR!r2h3vj+hrnRrjzn$yu0@=G zYV$H9x+NF6?w@!VYs&_Oa$FpvE>Tega|)Y9z!>8g6Go>_NWDQH=&^_MZcL;k32f3% zd7V`2jxyvudir$l@|YR62IS`MFbmaTn5EA16@emUOQw1&<4jocfZi=Mw9cG4lPpqh zzq|kI*C>_3;td8M&vUZReys8i$Lx=qkj~0Tk~vRoYU&R#!gF6SUM_g6%}B#FO`n5O zvmTE4#a_3Ea%9&=Y^OXZZVEnvsK0MqfNgQ9*KH1=^0A6rI-$6NDbeLT`C)Z-e+}zi z8Ue>_CA?@+#Iux;hrnPRu=G&)EZJc2dV=^MFof-Nb~cX}b18c{|B=gW=?dqs-c95p zd822GQ}D#JwX_CZe&7twcubPTu`BzyhMV85TIe~AlQz&V|(z zeO>9c?g@2-g6L>`iOMhVXK;2kHZ~3?S~a0N2}a7>+k2!e1*O$!(<0pRL?wyul?fub zlY&8+O9JP7JGJ&Q4Byz`$(W&$0kr_!K8Wx2%_nb^_oGfbckUde?D^YUzIIJi#qUSw z5qH^r7rN~H(ZXV~6zDOk9VI9YVR}4V?Bt{PW9u+`U@4o^rE$SC=MfUtf0A9`F4_8f zNB4?N>HLHvxwk&r<)TDK-5@y|DNcC#t1~<{$d#=J&3@zTn_Qjl?rtMC75}O#Ch!zg zal0N@hV0x#Lo+u&uj$`YrGhCw_nNk)IIQJ)bR^7#o z#5s)+Q}4FwwcjSzTbxKKBM{QY3F2Gx(*d;&69+acoL@g{{dPx6TfR(8qg{A&*uPS9 z;8oe9SP7D&y$LGfg=0BrIckqHn(s<-1?DGE*cpDj_R1B63kKw(azh)8z|i&|;P7wOotc7sHqs#9SsW)Yos{R?uuMrdIU!^pv&R z?r%A=o^WMuVs{Zwg;+Y0aQ*eaznQUDX|>zygZ6k%2-LKg2Du(Ku4;W@f?kwQ=%y>h z9d-mi1LCl7<<3i&E?Gyr(o0E79!lRG6!P+pA1I}K7B)7jf%>s#o}YHRch`|S(lhJUOixb>z=)SET7dqWU}Vym zkw?4m?gB|69Y~l*W!L>uQ>$oo3X5duWbuih8eUi6x@C^qRR>!i`D*;`j()x}%u^U;ztU9qy)`U_1G zF8g(&Z~Iu0`Ly!6F9ybutHYIkk&-B{LtG5##ueR#s#g`QpVP#bOiDXk3Gp>&n|Z7! z^Ze2~)~kV{Gs=rgOGH=YnKwt|c1%26{;7us)a~QXe);oSdr# zo{YHl0cLTosK7xmqm|aMBO)7}r_$7Y;OoDQj3lI|(_A)&BN)FvoQ+qUsYE1KhGllHN)uyoxy){^ZYmF}*rbXbC}f>yp~C`V$g+OVHdwXeKT z&Q7OeKE&`R>j82Y!)3}CPSyz zPfd-efY!7*cynluP(ry2O?i4cLjRz3-WBE3QfO(g zqtz11)~m+#Kp&_KPQi}tyAhwkzHtX{67r-!=ZKiZ$XMUk{=b8cH#tdeRHxJP1=XjH zuhx#v9yBxCM!t6SXNwCpM!xs%P*LIXC>rVB$aS*t@MC+iYM8jat6)Fio_zem{1F%!f`Fx)rD<`Lupm%wk*58ohN}(fZ=-MKC)G;(@iQ?!gFMDu z`=S<6S{qm8=Tp6Da?-kc*}zH)46Jnl^*^gU4E$_mXTQ9m5U6iiW6#sFs8`!K2agNi zf3mP8;3x0t#*Z2g0#^MEI265HZ_9=pEJ|9u(Su$C)6EI#!`OIa5UVIGyfFqf4vfO; zpFLY2(3jLB{@puB$rUr~qCMEvH^E63t&h**~g_3M8MS?#o1 zt`YQI;OK8@F4Nr9tFr&o9Jz>K$E+^C;*+P%lpWj6F0f0xzltCnj3W;-hX; z{V!0X(Ek!O24|pKoCEvkWreAmgJWawiY(Q=8$1nKI^@Ef4=EJ)*)4X*qbif%y?bwJ zw-Dt)C{PuA7hNlw?}efsIL(7?TzUT{nEog9nElA<7$e<^srI!GJVKT1#0406TMIYn zLZR~)evGbZfl+=~HaxG2nqltutm6R(8#u^%N9!^;7y~ zjnxNkb%mnpX83pcO`BHyOW&|0+bi!2+Dsx{x5D5$VY0$ztmW=5sf)!vJB&HBG&H7o zmZ!J=29+Wf{{WSJa(_VOp&c`EEHlH&5sT-wrYO&%I`fv1L6Ku2_%JIuB%L~~b~ws$ z!uY8p-n*9yJF)AbG*mBC5EUp|H`d!z${lrmwqwV3W#w&}FohjrkO!kSv}r9AaH=*o zxph8H+U1IV)`U@mz~=T7wg$0#&VOrlrg$DkTWo75^?r+G{Ui977+6q&-w(mbdE zeO>#TB_t#oH=GZ0tbe|!DN)Wp6V*dPLI(a9#*_+-T|wgmZQzRNkb=(P;o-)%30x|a zm&^R8*FysPJm4X$CKD*j(+DI#Yp-o(O39j^8|d`~3mfNNrhN>e-sj}xWMpLI>$?nV z7jP9IL2`cO)*c}te{LwsT60sW?POYgKoZ{sqTA@nE$K4il%~@2g4ZIKz zs2yC`>hzKR22nNvJ)hS>AION-t?G(bT#t>-E}YJ8XA7OF=MvdpwTFb5)$>n-L&ttN z;9-*vUkXJbO`!qB9KLhkaW}(y%$t<+1V~i)q)@!!z(fflPIb*SEeTk3G@l;NC3rLc?y(K zCWV_GjcEQ2IBj_n*0!%pdniX*NJw`ZIT{)o+cd>xWSA4+r^xIU=*(>fvJQGHz|1LD zF%|#iMVRSo+QWGeX}&R(S6cck-gTjxnc4N48Wp)*jPGCJl$TUiu10>IP@Q*7k)g0e zae(Oq2pXnf|Ec67yHS^~36nQLVfzFEs=opV=zaP8*%+ps(g4*1vLPq;9-Mpe_ARru zJK8uOBly6j*X_FDj05NIzXB04)M_laumgiKmm`R*_kZYbM_KEiChN-OIHoRh3blhS zY|U7{efwmg*#6W#=kDAbekRu=I}3jHz#FJ0Vky^@S%YAV*#4^4LpBm!NfeMWC@7TGM=x{ixuph@}jc~-(tJ{E#BG55>7SVo~trZeHV2d8?1K8grOk>P2 zA+D(Ci}ORs4hg9T@bFf1N|MJuv0v>%+qaW#L+fxE+bGn}8p&iIAthkK+TyoZTJXHE zVP|6t?|uRW1O{ejcnCD3^M7HXlT}{&y>`Tq4c~U2{O4e=VSX1Fn*40(;We;ZIXkf< z)Utq}fo&zx#MO^g^7pZYKOyO%&?O#R{c3(A2l`A5Kr(&_X!IE7zEE}SF9Ye0* zW(bOk9S>hu3>uI~Q~iD|0H0?eu~%KhzS85mLxYPTOx!^k5sGXfo4UVi_Ar}~VN^a7 z$z8h<+13fTC@HyF_cc^2{?iwki&>39V{mcRL1drgD76pjh-@lHy%Q&@;2wllL+f}e zH<7M#8bq7xwDtN814k6k_#_oqw4IH4GGZiJnk!GWe*Jn3UZAl|oZd_gz-~N|dkY^& zF*;!_ziZc3`~L53#L_}@Mja)M@HhLJP=uTOhu9BU@dL3E#ojeh#)P#bm+5RVyJw6o zTEUCC)5j@GdF-UNi-^$Ngca@*IgSSAR2-Zxg4+Bg-x=v(ku>@{Os?UFZ^^!l*{1qDHo z?Udp)Y8(9b5V>Zna~>!@q-P|B2`ftBL0OO4(SxWxBbl?>n3$O_hJ|4geicI{il!V?iDbAJg*I*dN=Aex@=qGTw}B(OEX+kV*3f zo!ok*FF8FfE=0mo*5mUVK7*hAA)Ac!{r#0AAc$!(Y_c=My28KprrKZI?F9-=OG`ss z_(>7>-tH+*FS9Jt1a#vi0jn$U?E@7{tBDeEV4k@ADMi9MjE71;S=sLbh=DU_zM=8k z?umh`LIFj^_f1U>7)eA`jyd)IF?q;nFFWFMU4;m{5=@u%3%aHYN%vrJ1qp%zekLs3 zjXIsRj=U&H9z_3INTB1N7?8U4aGg&T2BDOrie*7UgV zBTjrmLXL2vPuy%8yVG8_{@^#%>=VYpXq;SHTNm}94a=z3t-HujYBcF*R^=rTx&IZv zjconom9S96H1F}!J`o5Vx1latX6;BMK8uk@^cL^034pgOmdyTJ+Xl5 zR1DS7y!iLC9W+CrQKhF){d=M(`~TDhT|5liQwIiBdgd^wNn%NQtz-W`JF7NQt?#-9qF*e;2V*R9-K z>?+=8yg&+L0?lOO5)wv+hj#=-OXr~YhO>q~`N3M)tu^_@@eOuRzpZngp~qRT0-926}`p=B1O}C95WepYJjiZyQB{I zuCG+eIca1>fL{Y<0!79~MpU(u;Fwl8H}g+iL*(0`|3lUuj9X~hnb{aQJj-&YDKfMc zb6FVYZQ|whZnF|DsaxfV-dK9qyk^@kMgQdoibjqq>S&H(?KVERx%r;eQV85OU=g5`D zjMj=xNJ`+9@(n>RCMPGOlVo`Gt_zWl*nIf#E6_OH-v$S>ouvq@XUWpk$8cT(0EE0_ ztmOFSzoJKr&AR&`E5LkCt05Pf9-N$4mYt4?igH{-D~(3PQ%F`;7BrDIOAhLq_|#OP z`}~S?GgoiZ@?Qv!rZlAhntra|3%ktds`sSl!&1tpWM#{PGe_n%>!&zcnyuKO^-69M zzX{fHpq@X|Pr?NO%=%*nVl?{ndd*{+t@g=uIByT3-~*q{y|(@ohznn;7~=BaQIH>( zw4SEWv!nZrxEmW6hf@2hQ}VEpY-@(mR;3HlM`{%zfVD`A%CeI}Qzuz3Cnx6;x{uVv zp;OHWWs3lbpH`p5V-)u6!CYbN+s;oMGGExt=9Nk8y`F+ zKK*o^_`DT++^)3K-h!id-?K<1Cd9%@|lwvoWhI9VLyxBx`tPV13ZvLJEibWnt~QI z;Kwc?W+m^LhPQ8z)!UtU{~&02xr{gtY9U2n(D9R#pZTgQD{CD?^mJem6A_u{yv-;= z&ah+5d4!Os`3v;1)e=-y0vE6-x?A+or(4+Rb3f1XllO~G3dZV-WfQH8Sh(hbDs`q2 z7fNUD+%_^Y0VW*{RRdDg-QoW=w9zkfm*SIazbx1pZ25$f*H5qc2}!trdGLlp=#o*# z6&fgK1*4_DbLTA(XjV%|vfAp)U#o4JHv79Zci9cM@r_@+j51~LH-TI7m1hw|l|_oV z#DHQ_+Z1hH(9?QNWZV{|MoTlmd-d5D&IWrV|(t;wV zBCU@2_SbPdI1+q<2elx%f@(dMJa3Di*bkcKYBm%d#cyHI!$sLykvcb#HKyqpg8rCJ z0}}tsv=FEmv4Y^(Ht)+MVSjI%-5oK`AI~nYM`*kfUJ{#tCuM>TljS<$V+S07EVVy_ zizW!!)gq$u)Ez!~s<##EVWuj5GWMLCdbf-dAOnuv zayt4ccuj3jEY`oS+G|I=8Qu$J8Wt8eFl>}tlIGZ(-~?(omW8;g-TJ)i;p8cE%W|o^ zzhrq;=BoBNi*pObW)^au`*`E}jSP~ZXS5c#XKXJK0NWn?qB zm`f_PS-T5^t#rpx3-Rx(7BYToIoud?%6+^`wCM}ivcV;r4qp@mi{d-^`E~i;g%F%s zj67Xkv0t}ehCRaR3S77le2YYT_iuCb^T9q&YyoSivk>k z{8!J9J|MoAiPDt1x;li$!3RYO?gZKcu^$}1R5NcXpsny<_;;!9!&~V%~ z>>uxDX{FBLK41Nd$xQjlhPBJjC3%{4W2Mn=CD7EU#0NEPQ1z4&92I3{O~UlX3?6hQ zHZ$_uEhMCX-^|a?$MZF>@+t-vg+_2^ z@f6Z=DKEd^3V08W6|dN-R03v!+Z7c#rN*IMk^J53tbO_T)ZAT6ph1+h3`KBPp)v5T zwfDj<3ymW-F#q#IxtWjQtlt1r!F5T7wf93O;a;5`6UProKs;a(uE79BGn4@3#Mn^2!U;7PTiZ|gm^(9mFychF)@K) z&fREN0+cCmCV>s+jHS$t?bsrr=YcuM@IG8$Oj_7(n6_8?*<8uPVGUFeZZSB_@VT2- zBCVF($+RJm1H#W6oASdpq-BO587KOt0RLZli~k^T^t81__0mY1UgwjLm_~~S^Z?tg zLc;<>XIP5)cGP_^=)G4pR>47BFa25~ckp<5zck?lFiH0vC5M!@?aZbZQ>B4hMXg&0 z4p&?>Wk&H^EFB{FzcI5Stw)O3Pd=S!c^L_@cfyA?LX;gGtI9>-F*u{f zzF~i2NxB}-V#M<8(>R~u0@vLOGVZnlh3pm<-aoATrXQDzcSS>Oiwq8PBW$@^o@+*zqJ5ZlaM<6k6JCqIn2wp_=(za3gRwUBPRd) z^$E6Oq{24jV+sZijsmZ?^_D6m*b;y94}NS-$$v=+^=h5%h^Dfvy9~6VEdQ`&pEiy` zON5|yNJqze?o&)jE8WcOY)40jn0d8Gm0xnc2nhtd?s+pZb|KbyK!1y)Qy;d-fv6RA~1T7t%|Gmk*I$dI!rMG^pg-XHZXqR3VlIwD0 zw}vKZ6guj}FCpQE=9!Z>AZOZLY)zu$>O_mG_$?|Q-O=s?tm6t>paiVj-@TEN>D7kL zBm@a$K@>#$yUri0n+>3l!uF9&@Jjsrqo{fc_bKnI{KXJ z>AKbYlaDI~8AP@eZ9CIlqG^0XvY*DXZ+FklP&bm#4Q!$J;?w|iI`~|G&lFJ8A_L4Q zoTR3v#=I2NcE|pW_{i<%K_C?Vl1m{le3m6_akx_i-?Z< z<|<5>fqH6<;cav5KDf8B&R>nGsP%oYK2~gCNk+avZHqEUU`(y$Uoq*M2#+GR0B3fJ z^wK@}+TQdk$l=Pv=f(VUB&FyY+O(xrwbdP~48OCSIOVJqH6SsjHJrj8V~uzOcFxvC z;6a5fMT>3}^TnI|s}qH%`;H1kN$c$ZJv@_!2w+oGJeR_I(Op zm>J$M`4F8z@hku4>s~MD#?3jYHTCcrwSYY?_MQ8r&qSP#n>b`PQ356}_v0%Ff;z|U zAFzS~qPyDMB;>EQi>pAu4@#HlHvnOQh?Tx=4H$Cli4`wHzz;WF$(V~%o4zO0q_2Q@Q z(Bizh@xX;bHsm6Rm7E?(Tt{Si4+tpr@|7p;FPNVN56;s4xP%>f_ITA)$)EP&7_^K1 z{nz5-8-n56`=HXqQmRhk-R`c?Z=|>BP@25ok22pqNj6gNftB>J={oT@gJ!gq7rjdl z@QoYj=Fq>%d0qRf{vCdHpX}2SD&$c^zw(J=MtsD4$qS2a(@Li%fPW|-7 z3rcIgXhIVdRM=wflT;;tf;H{+|Mcr?osJ{w;a8WwMP0SO=?N1kU=LSUdNYCA;pW5X zt^fdzX}u3UcAD`0#zY4RenOkJDL(a8aU0Yy6y3S=MR&LHDXG8IWMxJ{ZTWoh6Oau0 zv#_u{di)q~AVvGeLF`<#T^TIHzNX%6JTu!;_9*LvpGceEBW3K6P;vFIgNO>o(B@dy zCNgVkjH*Y1%UXt)cL+#pS)&o-h?gDFExUMRN)g|pW9u<7tAerswHPKExmvIc!eSm# z3Bk7Qe)SJWh!(&q&O9_eBV_^RnMJsWJt}m8a3%N6VR5SlDu3Da@KEQj$65xj(2#?@ z$|@A8GEh&IHMg~$EVf$T^AdZlm<1``dw!b07Rp&8#vi1${~xBT2`-VBFMme4#{U^? z|DHX2h^8!sR37%&dR|&8r5uC^mb}(jQ93(fWS1dj zf7O{m-Pr?`w|m8AW@aowr;o=lDd#lNzrNu3rUD|@bswF7w=`9|+`*K{$Vl|A_2QH^ z8i{6A?QgYPy!(JH9b{RtZMnbNiS|qX0t<8F@1jM87l-2xBM338CNl=myTZW*5gWyI zV+7N>GYV&C!k;If<~*%tP68_QN>fUT;ZopBzzS@kO+>F72%i=NUFGHFeILDL7#PJ= zaJRz-uMK&gD|)+IyNDR#BP-RuFB`sU6JBA^6z0>}MMcCNXDDW{8u?yTnL&FL#N1nX z_$H#O*X>g53N<6W7yfDR$e`W0F)}(D3Ro-D6yPJo*@cA}JZHgL?2Vo>T}jNRUb}Ui zm4c)>0`Wieu(Vvtircr}fWIUt2ps66UE9rAJ{i@Ble{y58Z_v#6iC88UdBHFNKn#q zJD4NC%20rqGAS-DN2D|auN~`mpRq+j(%M`jcTWssS8&;&HC2YeTfl0}RT%pnnHD{t zElF}#_THAAE&J5xa!r=%nNqH8_06;3*Krs$XQ8-(-=L9kp^lr|w5iLJkx!r4=;`%f ztgvitDP|%WjF>s~owF(FTrR|NK$6Y*;gZp^(`!i-UVD4(&X_Bmb2_QLY{(Ly2Cez| z^WV{B*?2rN5-XvIYwAD5q{1f@DWYb1M66l&3x*+@ySq~|_yvW8_JEL5?N~->JtZ~` zDX2{WA=|9agMR|>Ur&&~Xqn3$wtDbYGUb4>O{ zZ$)fjbgufvva&Kv0?=)d{)BfBzI)Fe%zTHs?2?v_SMf7{iT>W}16Dku87a?iB9Sd{ z;NZVNvjvha)pzbh|iT*kXQ~Ee^r}3-TRlweQ%RKg(|_*zrZmGxN#I&nPCV zMD>M8jt-PwM^ow^*RK9JPUU}kV;f%fbfrHonex(boZLsRb(wN1Dk_F&F%ki;0HJ97 zRlrIqlp3QdO&w?t9I}ENt;Lr?oe6YGqY zp-&ZRRG{fMMO|pyB2XTG;+)!QajUt7!`P{$fucL}OE3c2eZZAB{}_nt?D!&59Auj2 zZDQc+bn;T#n1V-wrAqkrGd@Sf9|x9zVXA006MlkeM-i~w77uZE)$gM0xP|h%a7&)f zia*b@J6Z*}tf66HGlt;r$_Z6TuX}q9A+EH5vE8H$|0cn}`-vGZLPBD{@47)h-= z;=W$H{aLOST&JDicMhmRE^&1#GBnCAdRK7zER_n8Go;t2-+Mt2lNj$T5b7PrWC$WvAB+ zB{`r3zW5fFEDSB^>lqO%`iafeKfft7U0}U~oE7k^#Om1Hx43JfC67<30t@lToq69M zgPWq^^oo{H4|myiSqTaK60;XEW0#|&E8|bp>c4M=pF2_6qZ&ftz}loBW?m6s&o|z} z3&9gwMez25Nxi*y%-n?N(}+=YaT8-F9lPawYRmw6WtWAR6B|Gc6M!q)P<z55;`#JCic@5tyI7Ss8wJNbbnW+iiz&aAMddxXZ!X*^IHA0yz=tw z1rrg`8JaV;KhGzI41B=`APRQgfTbDRZApD=3s0@!XFPuNs3fSwqpZc%=erSH;BQ53`B}cw~ z_Cicb$^%v3gxCy7kOmASrDSHhJigPV>pv#4P7{U?%d`a0>wqh$)5cuVoW9tq?aN3--d1PDJ?38h;o%K>~{wB~kQnzck*&#%E*E zFNFd!lm78>#D(WI4z@lU#a%vPy_3IvSw_yuuzJVdF`{*ISlA%jfSgjtpS}0) zloQ=ZYdjLyY^@b(nG^zFeipQN=;0Y#M$w-Uk~{hMZX#T=2!m#ihR^dOG>_hv*zRwT zh^gPtR7sZO4#q_POMkz(YyV#V+xv6&ee_o7Tf9FxHI_A#=a-wLlP zzB{Gz&XVse$@o;z5oaxqW&LePfR=US3*i;$=n^+98#nE{uI$%bu^Tdr;NUU(`z2X! zIR#q!H|yu>bxEPpp|qx!mdJ<*YC1YB=jK9^hiQ@dveNas4j~FRbe1735WYTh#8Lld z4i9J7aZ(X3;5{_)Ye+EEe|G_IWOuhB${{;u#Kz8G2+5XRwMvrob!0hFY##djc|%%? z!ud1MSOi?7QLS<${Woq-dC>1eQGsw1JHN|d)23)_7Zng@Xec0k2@pkl_&wRDFAw2u zTiaIeGexd7PbzWWEg z7N@dua(DxZOKp@qjuLD1e1UD;;m1iQ$2y(tUbws%U#U4yLiidc{~o{6E^~hV*Shf7 z`RP@GRG8o;f<`S)zjjS8cBl^}GQ2zY5q~NafP~p(g(IG=MUl3yj z45Qt!L0?NVe(3@I2#Nf0jdR*VzxmGMb3u!DMFc33)i7cLs+0AeGAm4w;SN+w?6{

w*#CNRAD{+Q1Nm6fg)=^BD zL*s%c^M|4`U+#mu+?q}FuPhSfs7pl_RHW_8ety4iX z)YMxb5JW5-E|@UwuDH7w5nl)dLK~nhT8KD3nhmdCPlMp&J9$k_U7a}Xc3d8`v{o-# z<~j`6>dqqj1kx_ebPr$_^w8nu@mKg&gW5YVN3>`}wdL|Nf`meYzE(T|$Z`38 zz{UQ`igu@-GCbl9M|*HNiPrioiqHdK_z7L*i#i`5XDr3n`|Hz$}2-x zu=%(|$_7)Nt`711F7%Oba}K2M)-O>Od4N>FcXCk{d3`N^mz|SC6uI5SXlE%;R|@vWp1pxC6iasjN2q$xL(Wo}8pjS;6ZvOd zA_9hE01+3ye~Yp$!#?(4)2AxvTh!j^RuTC}`E%UOO(GaW-x_i7e3}}qP{`hWV9SdU ztk=UqL^vf_?ez16fk7F7>4c1@H%!gXdhdsVo<{56qu{5c+)*?1 zsC59OJZqudF}|M$G;Cv{56pz1gRHpnkcjoFQi;eKj+sNBJ|)9_($K@hgPRt!+7<;g zWg{++MVK3DS5EpajU+X%6$alHIBt<@FEviokuKe>{AhLa)0LaUR&6-<#jSl;1v#nb zsNJ6?>T@ZLv0k`_iryQ{f{`*N0iHR^Vh}pM(A^MrIfiX7Ee9}4Mf2pGo15x-s!LT= zJjW`}oUAA;=Jnjhx|?3>Gel5E&s{!kJioh~-_frcf)P4__f(E(JSUKXq<>aPUHcDh z4V7DOJ=kDR!^oj*CjwXO;C;v;VJ5gPhhXh*%)noRHu6lx%&{m#SR8Gjr5(8oQpvH{ zd*@BCy;Hhz^sRY~MbO^c1|^_Jo_!WwJ;h+-g*Lf9o0dG^vLx z$xcs%E4>&&qbJ;@r|j~v#~0>>4563LeXX&1-WYr(orlPTKbDD?ERu%dHK?doJkI1D z9Qd%k`(esCMI1-5Rs(z6h)PWjoYd*tX@4uGTyBFX$H6yJCj!UeN=2V-d{EHE z;5<+5!v*H42$(Ji(qWxn+P{l=ud?2S)fK;r3vi~uS@+Zq&z3^4HUi8PF)iN;`>zAu zF9wH(MEO`RFx|F$nf@6kC^J*cSS*w{*?&8CjGg6LM#feameKxx`K@vDl2PPVs`8440Nvih4p_GSkGRr;oR*Ss4YImUAN0sTznqP@9!cjyA8_mYe9Xj zSr&+K0zhNd1;ovhhTVP*5if60edC$DJU5|-eDdsw`)b43<#EuGok!cd9ajpg}|hI_LH9P z6nK&>x{1k&KSfy6%e?}IZ%vASYCSaFXUOX2={ahCT}VJ+YI>T%e|!$@Pr zN$n|Vhtz|q7w3;v@`=oqWU~fn+~eNcwM-mgeXfn8tWPF7H9vpO!(+%itK`XgqX)RV zNtjyY{Rm@lnnS1@2YA4=TCsPg5*C(_>$9@5cBd{Ld^I;j0>^JyO1%jP&3h8o97r@w zk(W0vth_%nE{LWN#x6ithYy|KyO)e7cHZ#lf-Sq}X0_rwU5@DfMeOW3S?$a2v9snl z)8=;EZh(NsadE<&;3|d8P9>!$kiD`9BmV>UplfC!^ID>{fhs#6VR>o?72|0HFIXLc ztU>EdproXPU?|>Ke-3xDJV)}6SAW*5LPLCD^OoTJ-PjHgx*ODv|FQ51-cA*~oksi5zsJ{9b*}#>{Z)<=KgK+~ z0c&rI5ML_Q;M*anTSD1jA||M&i}rh^p5QS;A2dZw99d}CB~XZ@F_wZnCh zV-@quQb3kwy+I&ncKiL=QFUPO_+QV#TMsfMad9GZpna8dN%;4Nr17l(uV)bd%*wdf z>Z007a@n}VLjPNaLQGMezRvW16_8 diff --git a/thread-local-storage/etc/thread-local-storage.urm.puml b/thread-local-storage/etc/thread-local-storage.urm.puml deleted file mode 100644 index f6e682a01..000000000 --- a/thread-local-storage/etc/thread-local-storage.urm.puml +++ /dev/null @@ -1,29 +0,0 @@ -@startuml -package com.iluwatar { - abstract class AbstractThreadLocalExample { - - RANDOM_THREAD_PARK_END : Integer {static} - - RANDOM_THREAD_PARK_START : Integer {static} - - RND : SecureRandom {static} - + AbstractThreadLocalExample() - - getThreadName() : String - # getter() : Supplier {abstract} - + run() - # setter() : Consumer {abstract} - } - class WithThreadLocal { - - value : ThreadLocal - + WithThreadLocal(value : ThreadLocal) - # getter() : Supplier - + remove() - # setter() : Consumer - } - class WithoutThreadLocal { - - value : Integer - + WithoutThreadLocal(value : Integer) - # getter() : Supplier - # setter() : Consumer - } -} -WithThreadLocal --|> AbstractThreadLocalExample -WithoutThreadLocal --|> AbstractThreadLocalExample -@enduml \ No newline at end of file diff --git a/thread-local-storage/pom.xml b/thread-local-storage/pom.xml deleted file mode 100644 index 49726e281..000000000 --- a/thread-local-storage/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - 4.0.0 - - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - - thread-local-storage - - - - org.junit.jupiter - junit-jupiter-engine - test - - - diff --git a/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java b/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java deleted file mode 100644 index 35726b37f..000000000 --- a/thread-local-storage/src/main/java/com/iluwatar/AbstractThreadLocalExample.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar; - -import java.security.SecureRandom; -import java.util.concurrent.locks.LockSupport; -import java.util.function.Consumer; -import java.util.function.Supplier; -import lombok.extern.slf4j.Slf4j; - -/** - * Class with main logic. - */ -@Slf4j -public abstract class AbstractThreadLocalExample implements Runnable { - - private static final SecureRandom RND = new SecureRandom(); - - private static final Integer RANDOM_THREAD_PARK_START = 1_000_000_000; - private static final Integer RANDOM_THREAD_PARK_END = 2_000_000_000; - - @Override - public void run() { - long nanosToPark = RND.nextInt(RANDOM_THREAD_PARK_START, RANDOM_THREAD_PARK_END); - LockSupport.parkNanos(nanosToPark); - - System.out.println(getThreadName() + ", before value changing: " + getter().get()); - setter().accept(RND.nextInt()); - } - - /** - * Setter for our value. - * - * @return consumer - */ - protected abstract Consumer setter(); - - /** - * Getter for our value. - * - * @return supplier - */ - protected abstract Supplier getter(); - - private String getThreadName() { - return Thread.currentThread().getName(); - } -} diff --git a/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java b/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java deleted file mode 100644 index d2b1482e7..000000000 --- a/thread-local-storage/src/main/java/com/iluwatar/WithThreadLocal.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar; - -import java.util.function.Consumer; -import java.util.function.Supplier; -import lombok.AllArgsConstructor; - -/** - * Example of runnable with use of {@link ThreadLocal}. - */ -@AllArgsConstructor -public class WithThreadLocal extends AbstractThreadLocalExample { - - private final ThreadLocal value; - - /** - * Removes the current thread's value for this thread-local variable. - */ - public void remove() { - this.value.remove(); - } - - @Override - protected Consumer setter() { - return value::set; - } - - @Override - protected Supplier getter() { - return value::get; - } -} diff --git a/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java b/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java deleted file mode 100644 index 76d30b7d4..000000000 --- a/thread-local-storage/src/main/java/com/iluwatar/WithoutThreadLocal.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar; - -import java.util.function.Consumer; -import java.util.function.Supplier; -import lombok.AllArgsConstructor; - -/** - * Example of runnable without usage of {@link ThreadLocal}. - */ -@AllArgsConstructor -public class WithoutThreadLocal extends AbstractThreadLocalExample { - - private Integer value; - - @Override - protected Consumer setter() { - return integer -> value = integer; - } - - @Override - protected Supplier getter() { - return () -> value; - } -} diff --git a/thread-local-storage/src/test/java/ThreadLocalTest.java b/thread-local-storage/src/test/java/ThreadLocalTest.java deleted file mode 100644 index 0de7f0514..000000000 --- a/thread-local-storage/src/test/java/ThreadLocalTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -import com.iluwatar.WithThreadLocal; -import com.iluwatar.WithoutThreadLocal; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -public class ThreadLocalTest { - - private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - private final PrintStream originalOut = System.out; - - @BeforeEach - public void setUpStreams() { - System.setOut(new PrintStream(outContent)); - } - - @AfterEach - public void restoreStreams() { - System.setOut(originalOut); - } - - @Test - public void withoutThreadLocal() throws InterruptedException { - int initialValue = 1234567890; - - int threadSize = 2; - ExecutorService executor = Executors.newFixedThreadPool(threadSize); - - for (int i = 0; i < threadSize; i++) { - //Create independent thread - WithoutThreadLocal threadLocal = new WithoutThreadLocal(initialValue); - executor.submit(threadLocal); - } - executor.awaitTermination(3, TimeUnit.SECONDS); - List lines = outContent.toString().lines().toList(); - - Assertions.assertTrue(lines.stream() - .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); - } - - @Test - public void withThreadLocal() throws InterruptedException { - int initialValue = 1234567890; - - int threadSize = 2; - ExecutorService executor = Executors.newFixedThreadPool(threadSize); - - WithThreadLocal threadLocal = new WithThreadLocal(ThreadLocal.withInitial(() -> initialValue)); - for (int i = 0; i < threadSize; i++) { - executor.submit(threadLocal); - } - - executor.awaitTermination(3, TimeUnit.SECONDS); - threadLocal.remove(); - - List lines = outContent.toString().lines().toList(); - Assertions.assertTrue(lines.stream() - .allMatch(line -> line.endsWith(String.valueOf(initialValue)))); - } -} diff --git a/thread-pool/README.md b/thread-pool/README.md deleted file mode 100644 index 685b39038..000000000 --- a/thread-pool/README.md +++ /dev/null @@ -1,219 +0,0 @@ ---- -title: Thread Pool -category: Concurrency -language: en -tag: - - Asynchronous - - Performance - - Resource management - - Scalability - - Synchronization - - Thread management ---- - -## Also known as - -* Worker Pool - -## Intent - -Efficiently manage a pool of worker threads to execute tasks concurrently, improving resource utilization and performance. - -## Explanation - -Real-world example - -> A real-world analogy for the Thread Pool design pattern can be found in a restaurant kitchen. Imagine a busy restaurant with a limited number of chefs (threads). Instead of hiring a new chef every time an order (task) comes in, the restaurant uses a fixed number of chefs to handle all the incoming orders. Each chef works on one order at a time and then moves on to the next one when finished. This approach ensures that the kitchen operates efficiently without the overhead of hiring and firing chefs continuously, and it prevents the kitchen from becoming overcrowded with too many chefs working at once. This setup allows the restaurant to handle multiple orders concurrently, optimize resource use, and maintain a steady workflow. - -In plain words - -> Thread Pool is a concurrency pattern where threads are allocated once and reused between tasks. - -Wikipedia says - -> In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program. Often also called a replicated workers or worker-crew model, a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program. By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks. The number of available threads is tuned to the computing resources available to the program, such as a parallel task queue after completion of execution. - -**Programmatic Example** - -Let's first look at our task hierarchy. We have an abstract base class `Task` and concrete tasks `CoffeeMakingTask` and `PotatoPeelingTask`. - -```java -public abstract class Task { - - private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); - - @Getter - private final int id; - @Getter - private final int timeMs; - - public Task(final int timeMs) { - this.id = ID_GENERATOR.incrementAndGet(); - this.timeMs = timeMs; - } - - @Override - public String toString() { - return String.format("id=%d timeMs=%d", id, timeMs); - } -} - -public class CoffeeMakingTask extends Task { - - private static final int TIME_PER_CUP = 100; - - public CoffeeMakingTask(int numCups) { - super(numCups * TIME_PER_CUP); - } - - @Override - public String toString() { - return String.format("%s %s", this.getClass().getSimpleName(), super.toString()); - } -} - -public class PotatoPeelingTask extends Task { - - private static final int TIME_PER_POTATO = 200; - - public PotatoPeelingTask(int numPotatoes) { - super(numPotatoes * TIME_PER_POTATO); - } - - @Override - public String toString() { - return String.format("%s %s", this.getClass().getSimpleName(), super.toString()); - } -} -``` - -Next, we present a runnable `Worker` class that the thread pool will utilize to handle all the potato peeling and coffee making. - -```java - -@Slf4j -public class Worker implements Runnable { - - private final Task task; - - public Worker(final Task task) { - this.task = task; - } - - @Override - public void run() { - LOGGER.info("{} processing {}", Thread.currentThread().getName(), task.toString()); - try { - Thread.sleep(task.getTimeMs()); - } catch (InterruptedException e) { - LOGGER.error("Error occurred: ", e); - } - } -} -``` - -Now, we are ready to show the full example in action. - -```java -LOGGER.info("Program started"); - -// Create a list of tasks to be executed -var tasks = List.of( - new PotatoPeelingTask(3), - new PotatoPeelingTask(6), - new CoffeeMakingTask(2), - new CoffeeMakingTask(6), - new PotatoPeelingTask(4), - new CoffeeMakingTask(2), - new PotatoPeelingTask(4), - new CoffeeMakingTask(9), - new PotatoPeelingTask(3), - new CoffeeMakingTask(2), - new PotatoPeelingTask(4), - new CoffeeMakingTask(2), - new CoffeeMakingTask(7), - new PotatoPeelingTask(4), - new PotatoPeelingTask(5)); - -// Creates a thread pool that reuses a fixed number of threads operating off a shared -// unbounded queue. At any point, at most nThreads threads will be active processing -// tasks. If additional tasks are submitted when all threads are active, they will wait -// in the queue until a thread is available. -var executor = Executors.newFixedThreadPool(3); - -// Allocate new worker for each task -// The worker is executed when a thread becomes -// available in the thread pool -tasks.stream().map(Worker::new).forEach(executor::execute); - -// All tasks were executed, now shutdown -executor.shutdown(); -while (!executor.isTerminated()) { - Thread.yield(); -} -LOGGER.info("Program finished"); -``` - -Running the example produces the following output: - -``` -13:47:07.244 [main] INFO com.iluwatar.threadpool.App -- Program started -13:47:07.258 [pool-1-thread-3] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-3 processing CoffeeMakingTask id=3 timeMs=200 -13:47:07.258 [pool-1-thread-2] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-2 processing PotatoPeelingTask id=2 timeMs=1200 -13:47:07.258 [pool-1-thread-1] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-1 processing PotatoPeelingTask id=1 timeMs=600 -13:47:07.464 [pool-1-thread-3] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-3 processing CoffeeMakingTask id=4 timeMs=600 -13:47:07.864 [pool-1-thread-1] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-1 processing PotatoPeelingTask id=5 timeMs=800 -13:47:08.066 [pool-1-thread-3] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-3 processing CoffeeMakingTask id=6 timeMs=200 -13:47:08.271 [pool-1-thread-3] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-3 processing PotatoPeelingTask id=7 timeMs=800 -13:47:08.464 [pool-1-thread-2] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-2 processing CoffeeMakingTask id=8 timeMs=900 -13:47:08.668 [pool-1-thread-1] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-1 processing PotatoPeelingTask id=9 timeMs=600 -13:47:09.076 [pool-1-thread-3] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-3 processing CoffeeMakingTask id=10 timeMs=200 -13:47:09.273 [pool-1-thread-1] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-1 processing PotatoPeelingTask id=11 timeMs=800 -13:47:09.277 [pool-1-thread-3] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-3 processing CoffeeMakingTask id=12 timeMs=200 -13:47:09.367 [pool-1-thread-2] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-2 processing CoffeeMakingTask id=13 timeMs=700 -13:47:09.482 [pool-1-thread-3] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-3 processing PotatoPeelingTask id=14 timeMs=800 -13:47:10.072 [pool-1-thread-2] INFO com.iluwatar.threadpool.Worker -- pool-1-thread-2 processing PotatoPeelingTask id=15 timeMs=1000 -13:47:11.078 [main] INFO com.iluwatar.threadpool.App -- Program finished -``` - -## Class diagram - -![Thread Pool](./etc/thread_pool_urm.png "Thread Pool") - -## Applicability - -* When there are multiple tasks to be executed and creating a new thread for each task would be inefficient. -* In scenarios where tasks are short-lived and the overhead of thread creation and destruction is significant. -* When you need to control the number of concurrent threads to prevent resource exhaustion. - -## Known Uses - -* Java's `java.util.concurrent.ExecutorService` and `ThreadPoolExecutor`. -* Web servers handling multiple client requests concurrently. -* Background task execution in GUI applications to keep the user interface responsive. - -## Consequences - -Benefits: - -* Improved performance by reusing existing threads instead of creating new ones. -* Better resource management by limiting the number of active threads. -* Simplifies the management of concurrent task execution. - -Trade-offs: - -* Requires careful tuning of the thread pool size to balance resource utilization and performance. -* Potential for thread starvation if the pool size is too small. -* Complexity in handling task rejection and thread lifecycle management. - -## Related Patterns - -* [Promise](https://java-design-patterns.com/patterns/promise/): Used to represent the result of an asynchronous computation, often executed by a thread pool. -* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Threads in the pool consume tasks produced by another part of the application. -* [Command](https://java-design-patterns.com/patterns/command/): Each task submitted to the thread pool can be treated as a command object. - -## Credits - -* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) -* [Effective Java](https://amzn.to/4cGk2Jz) -* [Java Concurrency in Practice](https://amzn.to/4aRMruW) diff --git a/thread-pool/etc/thread-pool.urm.puml b/thread-pool/etc/thread-pool.urm.puml deleted file mode 100644 index 251033c81..000000000 --- a/thread-pool/etc/thread-pool.urm.puml +++ /dev/null @@ -1,37 +0,0 @@ -@startuml -package com.iluwatar.threadpool { - class App { - - LOGGER : Logger {static} - + App() - + main(args : String[]) {static} - } - class CoffeeMakingTask { - - TIME_PER_CUP : int {static} - + CoffeeMakingTask(numCups : int) - + toString() : String - } - class PotatoPeelingTask { - - TIME_PER_POTATO : int {static} - + PotatoPeelingTask(numPotatoes : int) - + toString() : String - } - abstract class Task { - - ID_GENERATOR : AtomicInteger {static} - - id : int - - timeMs : int - + Task(timeMs : int) - + getId() : int - + getTimeMs() : int - + toString() : String - } - class Worker { - - LOGGER : Logger {static} - - task : Task - + Worker(task : Task) - + run() - } -} -Worker --> "-task" Task -CoffeeMakingTask --|> Task -PotatoPeelingTask --|> Task -@enduml \ No newline at end of file diff --git a/thread-pool/etc/thread_pool_urm.png b/thread-pool/etc/thread_pool_urm.png deleted file mode 100644 index 3d433824f95a5f9a9076e7094ac81a67a1d982e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30120 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 - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - thread-pool - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-core - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - - com.iluwatar.threadpool.App - - - - - - - - - diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/App.java b/thread-pool/src/main/java/com/iluwatar/threadpool/App.java deleted file mode 100644 index a2c3f9e3a..000000000 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/App.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import lombok.extern.slf4j.Slf4j; - -/** - * Thread Pool pattern is where a number of threads are created to perform a number of tasks, which - * are usually organized in a queue. The results from the tasks being executed might also be placed - * in a queue, or the tasks might return no result. Typically, there are many more tasks than - * threads. As soon as a thread completes its task, it will request the next task from the queue - * until all tasks have been completed. The thread can then terminate, or sleep until there are new - * tasks available. - * - *

In this example we create a list of tasks presenting work to be done. Each task is then - * wrapped into a {@link Worker} object that implements {@link Runnable}. We create an {@link - * ExecutorService} with fixed number of threads (Thread Pool) and use them to execute the {@link - * Worker}s. - */ -@Slf4j -public class App { - - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - - LOGGER.info("Program started"); - - // Create a list of tasks to be executed - var tasks = List.of( - new PotatoPeelingTask(3), - new PotatoPeelingTask(6), - new CoffeeMakingTask(2), - new CoffeeMakingTask(6), - new PotatoPeelingTask(4), - new CoffeeMakingTask(2), - new PotatoPeelingTask(4), - new CoffeeMakingTask(9), - new PotatoPeelingTask(3), - new CoffeeMakingTask(2), - new PotatoPeelingTask(4), - new CoffeeMakingTask(2), - new CoffeeMakingTask(7), - new PotatoPeelingTask(4), - new PotatoPeelingTask(5)); - - // Creates a thread pool that reuses a fixed number of threads operating off a shared - // unbounded queue. At any point, at most nThreads threads will be active processing - // tasks. If additional tasks are submitted when all threads are active, they will wait - // in the queue until a thread is available. - var executor = Executors.newFixedThreadPool(3); - - // Allocate new worker for each task - // The worker is executed when a thread becomes - // available in the thread pool - tasks.stream().map(Worker::new).forEach(executor::execute); - // All tasks were executed, now shutdown - executor.shutdown(); - while (!executor.isTerminated()) { - Thread.yield(); - } - LOGGER.info("Program finished"); - } -} diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java b/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java deleted file mode 100644 index 423b4de3b..000000000 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -/** - * CoffeeMakingTask is a concrete task. - */ -public class CoffeeMakingTask extends Task { - - private static final int TIME_PER_CUP = 100; - - public CoffeeMakingTask(int numCups) { - super(numCups * TIME_PER_CUP); - } - - @Override - public String toString() { - return String.format("%s %s", this.getClass().getSimpleName(), super.toString()); - } -} diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java b/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java deleted file mode 100644 index 24e86ed83..000000000 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -/** - * PotatoPeelingTask is a concrete task. - */ -public class PotatoPeelingTask extends Task { - - private static final int TIME_PER_POTATO = 200; - - public PotatoPeelingTask(int numPotatoes) { - super(numPotatoes * TIME_PER_POTATO); - } - - @Override - public String toString() { - return String.format("%s %s", this.getClass().getSimpleName(), super.toString()); - } -} diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java b/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java deleted file mode 100644 index 0a1224d80..000000000 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/Task.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -import java.util.concurrent.atomic.AtomicInteger; -import lombok.Getter; - -/** - * Abstract base class for tasks. - */ -public abstract class Task { - - private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); - - @Getter - private final int id; - @Getter - private final int timeMs; - - public Task(final int timeMs) { - this.id = ID_GENERATOR.incrementAndGet(); - this.timeMs = timeMs; - } - - @Override - public String toString() { - return String.format("id=%d timeMs=%d", id, timeMs); - } -} diff --git a/thread-pool/src/main/java/com/iluwatar/threadpool/Worker.java b/thread-pool/src/main/java/com/iluwatar/threadpool/Worker.java deleted file mode 100644 index f70e36f2d..000000000 --- a/thread-pool/src/main/java/com/iluwatar/threadpool/Worker.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -import lombok.extern.slf4j.Slf4j; - -/** - * Worker implements {@link Runnable} and thus can be executed by {@link - * java.util.concurrent.ExecutorService}. - */ -@Slf4j -public class Worker implements Runnable { - - private final Task task; - - public Worker(final Task task) { - this.task = task; - } - - @Override - public void run() { - LOGGER.info("{} processing {}", Thread.currentThread().getName(), task.toString()); - try { - Thread.sleep(task.getTimeMs()); - } catch (InterruptedException e) { - LOGGER.error("Error occurred: ", e); - } - } -} diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java deleted file mode 100644 index 3e91bbb7e..000000000 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/AppTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Application test - * - */ -class AppTest { - - @Test - void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } -} diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java deleted file mode 100644 index 5bc8c9177..000000000 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/CoffeeMakingTaskTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -/** - * CoffeeMakingTaskTest - * - */ -class CoffeeMakingTaskTest extends TaskTest { - - /** - * Create a new test instance - */ - public CoffeeMakingTaskTest() { - super(CoffeeMakingTask::new, 100); - } - -} diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java deleted file mode 100644 index 7c05cdd3f..000000000 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/PotatoPeelingTaskTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -/** - * PotatoPeelingTaskTest - * - */ -class PotatoPeelingTaskTest extends TaskTest { - - /** - * Create a new test instance - */ - public PotatoPeelingTaskTest() { - super(PotatoPeelingTask::new, 200); - } - -} \ No newline at end of file diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java deleted file mode 100644 index cbb972436..000000000 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/TaskTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -import static java.time.Duration.ofMillis; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTimeout; - -import java.util.ArrayList; -import java.util.Objects; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.function.IntFunction; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import org.junit.jupiter.api.Test; - -/** - * Test for Tasks using a Thread Pool - * - * @param Type of Task - */ -public abstract class TaskTest { - - /** - * The number of tasks used during the concurrency test - */ - private static final int TASK_COUNT = 128 * 1024; - - /** - * The number of threads used during the concurrency test - */ - private static final int THREAD_COUNT = 8; - - /** - * The task factory, used to create new test items - */ - private final IntFunction factory; - - /** - * The expected time needed to run the task 1 single time, in milli seconds - */ - private final int expectedExecutionTime; - - /** - * Create a new test instance - * - * @param factory The task factory, used to create new test items - * @param expectedExecutionTime The expected time needed to run the task 1 time, in milli seconds - */ - public TaskTest(final IntFunction factory, final int expectedExecutionTime) { - this.factory = factory; - this.expectedExecutionTime = expectedExecutionTime; - } - - /** - * Verify if the generated id is unique for each task, even if the tasks are created in separate - * threads - */ - @Test - void testIdGeneration() { - assertTimeout(ofMillis(10000), () -> { - final var service = Executors.newFixedThreadPool(THREAD_COUNT); - - final var tasks = IntStream.range(0, TASK_COUNT) - .>mapToObj(i -> () -> factory.apply(1).getId()) - .collect(Collectors.toCollection(ArrayList::new)); - - final var ids = service.invokeAll(tasks) - .stream() - .map(TaskTest::get) - .filter(Objects::nonNull) - .toList(); - - service.shutdownNow(); - - final var uniqueIdCount = ids.stream() - .distinct() - .count(); - - assertEquals(TASK_COUNT, ids.size()); - assertEquals(TASK_COUNT, uniqueIdCount); - }); - } - - /** - * Verify if the time per execution of a task matches the actual time required to execute the task - * a given number of times - */ - @Test - void testTimeMs() { - for (var i = 0; i < 10; i++) { - assertEquals(this.expectedExecutionTime * i, this.factory.apply(i).getTimeMs()); - } - } - - /** - * Verify if the task has some sort of {@link T#toString()}, different from 'null' - */ - @Test - void testToString() { - assertNotNull(this.factory.apply(0).toString()); - } - - /** - * Extract the result from a future or returns 'null' when an exception occurred - * - * @param future The future we want the result from - * @param The result type - * @return The result or 'null' when a checked exception occurred - */ - private static O get(Future future) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - return null; - } - } - -} diff --git a/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java b/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java deleted file mode 100644 index c559f36ea..000000000 --- a/thread-pool/src/test/java/com/iluwatar/threadpool/WorkerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.threadpool; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import org.junit.jupiter.api.Test; - -/** - * WorkerTest - * - */ -class WorkerTest { - - /** - * Verify if a worker does the actual job - */ - @Test - void testRun() { - final var task = mock(Task.class); - final var worker = new Worker(task); - verifyNoMoreInteractions(task); - - worker.run(); - verify(task).getTimeMs(); - verifyNoMoreInteractions(task); - } - -} \ No newline at end of file