├── .gitignore ├── LICENCE.txt ├── Readme.md ├── files └── thread-dump-01.json ├── pom.xml ├── runConfigurations ├── A_StartingThreads.xml ├── B_ThreadBuilders.xml ├── C_MaxPlatformThreads.xml ├── D_MaxVirtualThreads.xml ├── E_HowManyVirtualThreads.xml ├── F0_SleepingThreads.xml ├── F1_SyncedThread.xml ├── F3_LockedThread.xml ├── G0_ContinuationYield.xml ├── G1_ContinuationYield.xml ├── G2_ContinuationYield.xml ├── G3_ContinuationYield.xml ├── H0_ExtentLocal.xml ├── H1_ExtentLocal_ContinuationYield.xml ├── H2_ExtentLocal_ContinuationYield.xml └── TravelPageExample.xml └── src └── main └── java └── org └── paumard └── loom ├── threads ├── A_StartingThreads.java ├── B_ThreadBuilders.java ├── C_MaxPlatformThreads.java ├── D_MaxVirtualThreads.java ├── E_HowManyVirtualThreads.java ├── F0_SleepingThreads.java ├── F1_SyncedThread.java ├── F3_LockedThread.java ├── G0_ContinuationYield.java ├── G1_ContinuationYield.java ├── G2_ContinuationYield.java ├── G3_ContinuationYield.java ├── H0_ExtentLocal.java ├── H1_ExtentLocal_ContinuationYield.java ├── H2_ExtentLocal_ContinuationYield.java └── Main.java └── travelpage ├── TravelPageExample.java └── model ├── PageComponent.java ├── Quotation.java ├── TravelPage.java └── Weather.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | out/ -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Oracle 2 | 3 | The Universal Permissive License (UPL), Version 1.0 4 | 5 | Subject to the condition set forth below, permission is hereby granted to any person obtaining a copy of this software, associated documentation and/or data (collectively the "Software"), free of charge and under any and all copyright rights in the Software, and any and all patent rights owned or freely licensable by each licensor hereunder covering either (i) the unmodified Software as contributed to or provided by such licensor, or (ii) the Larger Works (as defined below), to deal in both 6 | 7 | (a) the Software, and 8 | 9 | (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if one is included with the Software (each a “Larger Work” to which the Software is contributed by such licensors), 10 | 11 | without restriction, including without limitation the rights to copy, create derivative works of, display, perform, and distribute the Software and make, use, sell, offer for sale, import, export, have made, and have sold the Software and the Larger Work(s), and to sublicense the foregoing rights on either these or other terms. 12 | 13 | This license is subject to the following condition: 14 | 15 | The above copyright notice and either this complete permission notice or at a minimum a reference to the UPL must be included in all copies or substantial portions of the Software. 16 | 17 | 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. 18 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Loom Demo repository 2 | ==================== 3 | 4 | This is the repository in which I push the demos I present along with my presentations about the Loom project. 5 | 6 | You can learn more about the Loom project and download the early access builds here: http://jdk.java.net/loom/ 7 | 8 | You can find the slides of the talk we did with Rémi Forax here: https://speakerdeck.com/josepaumard/loom-nous-protegera-t-il-du-braquage-temporel and see the presentation here: https://youtu.be/wx7t69DylsI 9 | 10 | You can find the slides of my [Devoxx UK](https://www.devoxx.co.uk/) talk here: https://speakerdeck.com/josepaumard/loom-is-blooming 11 | 12 | The version of Loom you need to run the examples is `jdk-19-loom+6-625`. 13 | 14 | The `runConfigurations` directory contains the run configurations you can use to run the various examples. They have been created using IntelliJ IDEA and may not be working with other IDEs. -------------------------------------------------------------------------------- /files/thread-dump-01.json: -------------------------------------------------------------------------------- 1 | { 2 | "threadDump": { 3 | "processId": 108932, 4 | "time": "2022-05-11T15:16:14.791320100Z", 5 | "runtimeVersion": "19-loom+6-625", 6 | "threadContainers": [ 7 | { 8 | "container": "", 9 | "parent": null, 10 | "owner": null, 11 | "threads": [ 12 | { 13 | "tid": 1, 14 | "name": "main", 15 | "stack": [ 16 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 17 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 18 | "java.base\/jdk.internal.misc.ThreadFlock.awaitAll(ThreadFlock.java:315)", 19 | "jdk.incubator.concurrent\/jdk.incubator.concurrent.StructuredTaskScope.implJoin(StructuredTaskScope.java:486)", 20 | "jdk.incubator.concurrent\/jdk.incubator.concurrent.StructuredTaskScope.join(StructuredTaskScope.java:506)", 21 | "org.paumard.loom.travelpage.TravelPageExample.main(TravelPageExample.java:65)" 22 | ] 23 | }, 24 | { 25 | "tid": 8, 26 | "name": "Reference Handler", 27 | "stack": [ 28 | "java.base\/java.lang.ref.Reference.waitForReferencePendingList(Native Method)", 29 | "java.base\/java.lang.ref.Reference.processPendingReferences(Reference.java:244)", 30 | "java.base\/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:206)" 31 | ] 32 | }, 33 | { 34 | "tid": 9, 35 | "name": "Finalizer", 36 | "stack": [ 37 | "java.base\/java.lang.Object.wait0(Native Method)", 38 | "java.base\/java.lang.Object.wait(Object.java:366)", 39 | "java.base\/java.lang.Object.wait(Object.java:339)", 40 | "java.base\/java.lang.ref.NativeReferenceQueue.await(NativeReferenceQueue.java:48)", 41 | "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:157)", 42 | "java.base\/java.lang.ref.NativeReferenceQueue.remove(NativeReferenceQueue.java:89)", 43 | "java.base\/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:173)" 44 | ] 45 | }, 46 | { 47 | "tid": 10, 48 | "name": "Signal Dispatcher", 49 | "stack": [ 50 | ] 51 | }, 52 | { 53 | "tid": 11, 54 | "name": "Attach Listener", 55 | "stack": [ 56 | "java.base\/java.lang.Thread.getStackTrace(Thread.java:2545)", 57 | "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadToJson(ThreadDumper.java:262)", 58 | "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:237)", 59 | "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:201)", 60 | "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToFile(ThreadDumper.java:115)", 61 | "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:84)" 62 | ] 63 | }, 64 | { 65 | "tid": 19, 66 | "name": "Common-Cleaner", 67 | "stack": [ 68 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 69 | "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", 70 | "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1759)", 71 | "java.base\/java.lang.ref.ReferenceQueue.await(ReferenceQueue.java:70)", 72 | "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:142)", 73 | "java.base\/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:217)", 74 | "java.base\/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)", 75 | "java.base\/java.lang.Thread.run(Thread.java:1584)", 76 | "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" 77 | ] 78 | }, 79 | { 80 | "tid": 20, 81 | "name": "Monitor Ctrl-Break", 82 | "stack": [ 83 | "java.base\/sun.nio.ch.SocketDispatcher.read0(Native Method)", 84 | "java.base\/sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:46)", 85 | "java.base\/sun.nio.ch.NioSocketImpl.tryRead(NioSocketImpl.java:251)", 86 | "java.base\/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:302)", 87 | "java.base\/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:340)", 88 | "java.base\/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:789)", 89 | "java.base\/java.net.Socket$SocketInputStream.read(Socket.java:1025)", 90 | "java.base\/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:327)", 91 | "java.base\/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:370)", 92 | "java.base\/sun.nio.cs.StreamDecoder.lockedRead(StreamDecoder.java:218)", 93 | "java.base\/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:173)", 94 | "java.base\/java.io.InputStreamReader.read(InputStreamReader.java:189)", 95 | "java.base\/java.io.BufferedReader.fill(BufferedReader.java:161)", 96 | "java.base\/java.io.BufferedReader.implReadLine(BufferedReader.java:371)", 97 | "java.base\/java.io.BufferedReader.readLine(BufferedReader.java:348)", 98 | "java.base\/java.io.BufferedReader.readLine(BufferedReader.java:437)", 99 | "com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:49)" 100 | ] 101 | }, 102 | { 103 | "tid": 21, 104 | "name": "Notification Thread", 105 | "stack": [ 106 | ] 107 | }, 108 | { 109 | "tid": 40, 110 | "name": "Read-Poller", 111 | "stack": [ 112 | "java.base\/sun.nio.ch.WEPoll.wait(Native Method)", 113 | "java.base\/sun.nio.ch.WEPollPoller.poll(WEPollPoller.java:65)", 114 | "java.base\/sun.nio.ch.Poller.poll(Poller.java:363)", 115 | "java.base\/sun.nio.ch.Poller.pollLoop(Poller.java:270)", 116 | "java.base\/java.lang.Thread.run(Thread.java:1584)", 117 | "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" 118 | ] 119 | }, 120 | { 121 | "tid": 41, 122 | "name": "Read-Updater", 123 | "stack": [ 124 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 125 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 126 | "java.base\/java.util.concurrent.LinkedTransferQueue$Node.block(LinkedTransferQueue.java:470)", 127 | "java.base\/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3697)", 128 | "java.base\/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3642)", 129 | "java.base\/java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:669)", 130 | "java.base\/java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:616)", 131 | "java.base\/java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1286)", 132 | "java.base\/sun.nio.ch.Poller.updateLoop(Poller.java:286)", 133 | "java.base\/java.lang.Thread.run(Thread.java:1584)", 134 | "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" 135 | ] 136 | }, 137 | { 138 | "tid": 42, 139 | "name": "Write-Poller", 140 | "stack": [ 141 | "java.base\/sun.nio.ch.WEPoll.wait(Native Method)", 142 | "java.base\/sun.nio.ch.WEPollPoller.poll(WEPollPoller.java:65)", 143 | "java.base\/sun.nio.ch.Poller.poll(Poller.java:363)", 144 | "java.base\/sun.nio.ch.Poller.pollLoop(Poller.java:270)", 145 | "java.base\/java.lang.Thread.run(Thread.java:1584)", 146 | "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" 147 | ] 148 | }, 149 | { 150 | "tid": 43, 151 | "name": "Write-Updater", 152 | "stack": [ 153 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 154 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 155 | "java.base\/java.util.concurrent.LinkedTransferQueue$Node.block(LinkedTransferQueue.java:470)", 156 | "java.base\/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3697)", 157 | "java.base\/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3642)", 158 | "java.base\/java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:669)", 159 | "java.base\/java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:616)", 160 | "java.base\/java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1286)", 161 | "java.base\/sun.nio.ch.Poller.updateLoop(Poller.java:286)", 162 | "java.base\/java.lang.Thread.run(Thread.java:1584)", 163 | "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" 164 | ] 165 | } 166 | ], 167 | "threadCount": 12 168 | }, 169 | { 170 | "container": "ForkJoinPool.commonPool\/jdk.internal.vm.SharedThreadContainer@346f5316", 171 | "parent": "", 172 | "owner": null, 173 | "threads": [ 174 | ], 175 | "threadCount": 0 176 | }, 177 | { 178 | "container": "ForkJoinPool-1\/jdk.internal.vm.SharedThreadContainer@1fe20a74", 179 | "parent": "", 180 | "owner": null, 181 | "threads": [ 182 | { 183 | "tid": 24, 184 | "name": "ForkJoinPool-1-worker-1", 185 | "stack": [ 186 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 187 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 188 | "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1890)", 189 | "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1807)", 190 | "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)" 191 | ] 192 | }, 193 | { 194 | "tid": 26, 195 | "name": "ForkJoinPool-1-worker-2", 196 | "stack": [ 197 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 198 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 199 | "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1890)", 200 | "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1807)", 201 | "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)" 202 | ] 203 | }, 204 | { 205 | "tid": 27, 206 | "name": "ForkJoinPool-1-worker-3", 207 | "stack": [ 208 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 209 | "java.base\/java.util.concurrent.locks.LockSupport.parkUntil(LockSupport.java:449)", 210 | "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1888)", 211 | "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1807)", 212 | "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)" 213 | ] 214 | }, 215 | { 216 | "tid": 28, 217 | "name": "ForkJoinPool-1-worker-4", 218 | "stack": [ 219 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 220 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 221 | "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1890)", 222 | "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1807)", 223 | "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)" 224 | ] 225 | }, 226 | { 227 | "tid": 29, 228 | "name": "ForkJoinPool-1-worker-5", 229 | "stack": [ 230 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 231 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 232 | "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1890)", 233 | "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1807)", 234 | "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)" 235 | ] 236 | }, 237 | { 238 | "tid": 34, 239 | "name": "ForkJoinPool-1-worker-6", 240 | "stack": [ 241 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 242 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 243 | "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1890)", 244 | "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1807)", 245 | "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)" 246 | ] 247 | }, 248 | { 249 | "tid": 36, 250 | "name": "ForkJoinPool-1-worker-7", 251 | "stack": [ 252 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 253 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 254 | "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1890)", 255 | "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1807)", 256 | "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)" 257 | ] 258 | }, 259 | { 260 | "tid": 38, 261 | "name": "ForkJoinPool-1-worker-8", 262 | "stack": [ 263 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 264 | "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", 265 | "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1890)", 266 | "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1807)", 267 | "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)" 268 | ] 269 | } 270 | ], 271 | "threadCount": 8 272 | }, 273 | { 274 | "container": "java.util.concurrent.ScheduledThreadPoolExecutor@824318946\/jdk.internal.vm.SharedThreadContainer@77cc68fb", 275 | "parent": "", 276 | "owner": null, 277 | "threads": [ 278 | { 279 | "tid": 39, 280 | "name": "VirtualThread-unparker", 281 | "stack": [ 282 | "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", 283 | "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", 284 | "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1674)", 285 | "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)", 286 | "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)", 287 | "java.base\/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1069)", 288 | "java.base\/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1129)", 289 | "java.base\/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)", 290 | "java.base\/java.lang.Thread.run(Thread.java:1584)", 291 | "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" 292 | ] 293 | } 294 | ], 295 | "threadCount": 1 296 | }, 297 | { 298 | "container": "java.util.concurrent.ThreadPoolExecutor@122883338\/jdk.internal.vm.SharedThreadContainer@759b1178", 299 | "parent": "", 300 | "owner": null, 301 | "threads": [ 302 | ], 303 | "threadCount": 0 304 | }, 305 | { 306 | "container": "jdk.internal.misc.ThreadFlock@3b221b3e", 307 | "parent": "", 308 | "owner": 1, 309 | "threads": [ 310 | { 311 | "tid": 23, 312 | "name": "", 313 | "stack": [ 314 | "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:356)", 315 | "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)", 316 | "java.base\/java.lang.VirtualThread.doParkNanos(VirtualThread.java:552)", 317 | "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:523)", 318 | "java.base\/java.lang.System$2.parkVirtualThread(System.java:2585)", 319 | "java.base\/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:67)", 320 | "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:408)", 321 | "java.base\/jdk.internal.misc.ThreadFlock.awaitAll(ThreadFlock.java:354)", 322 | "jdk.incubator.concurrent\/jdk.incubator.concurrent.StructuredTaskScope.implJoin(StructuredTaskScope.java:484)", 323 | "jdk.incubator.concurrent\/jdk.incubator.concurrent.StructuredTaskScope.joinUntil(StructuredTaskScope.java:533)", 324 | "jdk.incubator.concurrent\/jdk.incubator.concurrent.StructuredTaskScope$ShutdownOnSuccess.joinUntil(StructuredTaskScope.java:921)", 325 | "org.paumard.loom.travelpage.model.Weather$WeatherScope.joinUntil(Weather.java:26)", 326 | "org.paumard.loom.travelpage.model.Weather.readWeather(Weather.java:71)", 327 | "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)", 328 | "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)", 329 | "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)", 330 | "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:326)", 331 | "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:319)" 332 | ] 333 | }, 334 | { 335 | "tid": 25, 336 | "name": "", 337 | "stack": [ 338 | "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:356)", 339 | "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)", 340 | "java.base\/java.lang.VirtualThread.doParkNanos(VirtualThread.java:552)", 341 | "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:523)", 342 | "java.base\/java.lang.System$2.parkVirtualThread(System.java:2585)", 343 | "java.base\/jdk.internal.misc.VirtualThreads.park(VirtualThreads.java:67)", 344 | "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:408)", 345 | "java.base\/jdk.internal.misc.ThreadFlock.awaitAll(ThreadFlock.java:354)", 346 | "jdk.incubator.concurrent\/jdk.incubator.concurrent.StructuredTaskScope.implJoin(StructuredTaskScope.java:484)", 347 | "jdk.incubator.concurrent\/jdk.incubator.concurrent.StructuredTaskScope.joinUntil(StructuredTaskScope.java:533)", 348 | "org.paumard.loom.travelpage.model.Quotation$QuotationScope.joinUntil(Quotation.java:40)", 349 | "org.paumard.loom.travelpage.model.Quotation.readQuotation(Quotation.java:81)", 350 | "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)", 351 | "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)", 352 | "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)", 353 | "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:326)", 354 | "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:319)" 355 | ] 356 | } 357 | ], 358 | "threadCount": 2 359 | }, 360 | { 361 | "container": "jdk.internal.misc.ThreadFlock@12a5cd7f", 362 | "parent": "jdk.internal.misc.ThreadFlock@3b221b3e", 363 | "owner": 23, 364 | "threads": [ 365 | { 366 | "tid": 33, 367 | "name": "", 368 | "stack": [ 369 | "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:356)", 370 | "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)", 371 | "java.base\/java.lang.VirtualThread.doParkNanos(VirtualThread.java:552)", 372 | "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:523)", 373 | "java.base\/java.lang.VirtualThread.doSleepNanos(VirtualThread.java:735)", 374 | "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:708)", 375 | "java.base\/java.lang.Thread.sleep(Thread.java:535)", 376 | "org.paumard.loom.travelpage.model.Weather.lambda$readWeather$2(Weather.java:67)", 377 | "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)", 378 | "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)", 379 | "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)", 380 | "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:326)", 381 | "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:319)" 382 | ] 383 | }, 384 | { 385 | "tid": 30, 386 | "name": "", 387 | "stack": [ 388 | "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:356)", 389 | "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)", 390 | "java.base\/java.lang.VirtualThread.doParkNanos(VirtualThread.java:552)", 391 | "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:523)", 392 | "java.base\/java.lang.VirtualThread.doSleepNanos(VirtualThread.java:735)", 393 | "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:708)", 394 | "java.base\/java.lang.Thread.sleep(Thread.java:535)", 395 | "org.paumard.loom.travelpage.model.Weather.lambda$readWeather$0(Weather.java:59)", 396 | "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)", 397 | "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)", 398 | "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)", 399 | "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:326)", 400 | "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:319)" 401 | ] 402 | }, 403 | { 404 | "tid": 31, 405 | "name": "", 406 | "stack": [ 407 | "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:356)", 408 | "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)", 409 | "java.base\/java.lang.VirtualThread.doParkNanos(VirtualThread.java:552)", 410 | "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:523)", 411 | "java.base\/java.lang.VirtualThread.doSleepNanos(VirtualThread.java:735)", 412 | "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:708)", 413 | "java.base\/java.lang.Thread.sleep(Thread.java:535)", 414 | "org.paumard.loom.travelpage.model.Weather.lambda$readWeather$1(Weather.java:63)", 415 | "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)", 416 | "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)", 417 | "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)", 418 | "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:326)", 419 | "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:319)" 420 | ] 421 | } 422 | ], 423 | "threadCount": 3 424 | }, 425 | { 426 | "container": "jdk.internal.misc.ThreadFlock@9e66bed", 427 | "parent": "jdk.internal.misc.ThreadFlock@3b221b3e", 428 | "owner": 25, 429 | "threads": [ 430 | { 431 | "tid": 32, 432 | "name": "", 433 | "stack": [ 434 | "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:356)", 435 | "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)", 436 | "java.base\/java.lang.VirtualThread.doParkNanos(VirtualThread.java:552)", 437 | "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:523)", 438 | "java.base\/java.lang.VirtualThread.doSleepNanos(VirtualThread.java:735)", 439 | "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:708)", 440 | "java.base\/java.lang.Thread.sleep(Thread.java:535)", 441 | "org.paumard.loom.travelpage.model.Quotation.lambda$readQuotation$0(Quotation.java:69)", 442 | "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)", 443 | "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)", 444 | "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)", 445 | "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:326)", 446 | "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:319)" 447 | ] 448 | }, 449 | { 450 | "tid": 35, 451 | "name": "", 452 | "stack": [ 453 | "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:356)", 454 | "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)", 455 | "java.base\/java.lang.VirtualThread.doParkNanos(VirtualThread.java:552)", 456 | "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:523)", 457 | "java.base\/java.lang.VirtualThread.doSleepNanos(VirtualThread.java:735)", 458 | "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:708)", 459 | "java.base\/java.lang.Thread.sleep(Thread.java:535)", 460 | "org.paumard.loom.travelpage.model.Quotation.lambda$readQuotation$1(Quotation.java:73)", 461 | "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)", 462 | "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)", 463 | "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)", 464 | "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:326)", 465 | "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:319)" 466 | ] 467 | }, 468 | { 469 | "tid": 37, 470 | "name": "", 471 | "stack": [ 472 | "java.base\/jdk.internal.vm.Continuation.yield(Continuation.java:356)", 473 | "java.base\/java.lang.VirtualThread.yieldContinuation(VirtualThread.java:370)", 474 | "java.base\/java.lang.VirtualThread.doParkNanos(VirtualThread.java:552)", 475 | "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:523)", 476 | "java.base\/java.lang.VirtualThread.doSleepNanos(VirtualThread.java:735)", 477 | "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:708)", 478 | "java.base\/java.lang.Thread.sleep(Thread.java:535)", 479 | "org.paumard.loom.travelpage.model.Quotation.lambda$readQuotation$2(Quotation.java:77)", 480 | "java.base\/java.util.concurrent.FutureTask.run(FutureTask.java:317)", 481 | "java.base\/java.lang.VirtualThread.run(VirtualThread.java:287)", 482 | "java.base\/java.lang.VirtualThread$VThreadContinuation.lambda$new$0(VirtualThread.java:174)", 483 | "java.base\/jdk.internal.vm.Continuation.enter0(Continuation.java:326)", 484 | "java.base\/jdk.internal.vm.Continuation.enter(Continuation.java:319)" 485 | ] 486 | } 487 | ], 488 | "threadCount": 3 489 | } 490 | ] 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.paumard 8 | devoxxuk-2022 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 19 13 | 19 14 | 15 | 16 | -------------------------------------------------------------------------------- /runConfigurations/A_StartingThreads.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /runConfigurations/B_ThreadBuilders.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/C_MaxPlatformThreads.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/D_MaxVirtualThreads.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/E_HowManyVirtualThreads.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/F0_SleepingThreads.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/F1_SyncedThread.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/F3_LockedThread.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/G0_ContinuationYield.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/G1_ContinuationYield.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/G2_ContinuationYield.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/G3_ContinuationYield.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/H0_ExtentLocal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/H1_ExtentLocal_ContinuationYield.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/H2_ExtentLocal_ContinuationYield.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /runConfigurations/TravelPageExample.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/A_StartingThreads.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | public class A_StartingThreads { 4 | 5 | // --enable-preview 6 | 7 | public static void main(String[] args) throws InterruptedException { 8 | 9 | // platform threads 10 | Thread pthread = new Thread(() -> { 11 | System.out.println("platform: " + Thread.currentThread()); 12 | }); 13 | pthread.start(); 14 | pthread.join(); 15 | 16 | // virtual threads 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/B_ThreadBuilders.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | public class B_ThreadBuilders { 4 | 5 | // --enable-preview 6 | 7 | public static void main(String[] args) throws InterruptedException { 8 | // platform thread 9 | var pthread = Thread.ofPlatform() 10 | .name("platform-", 0) 11 | .start(() -> { 12 | System.out.println("platform " + Thread.currentThread()); 13 | }); 14 | pthread.join(); 15 | 16 | // virtual thread 17 | var vthread = Thread.ofVirtual() 18 | .name("virtual-", 0) 19 | .start(() -> { 20 | System.out.println("virtual " + Thread.currentThread()); 21 | }); 22 | vthread.join(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/C_MaxPlatformThreads.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.stream.IntStream; 6 | 7 | public class C_MaxPlatformThreads { 8 | 9 | // --enable-preview 10 | 11 | public static void main(String[] args) throws InterruptedException { 12 | 13 | // platform thread 14 | var threads = 15 | IntStream.range(0, 100) 16 | .mapToObj(index -> 17 | Thread.ofPlatform() 18 | .name("platform-", index) 19 | .unstarted(() -> { 20 | try { 21 | Thread.sleep(2_000); 22 | } catch (InterruptedException e) { 23 | throw new RuntimeException(e); 24 | } 25 | })) 26 | .toList(); 27 | 28 | Instant begin = Instant.now(); 29 | threads.forEach(Thread::start); 30 | for (Thread thread : threads) { 31 | thread.join(); 32 | } 33 | Instant end = Instant.now(); 34 | System.out.println("Duration = " + Duration.between(begin, end)); 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/D_MaxVirtualThreads.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.Set; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | import java.util.stream.IntStream; 10 | 11 | public class D_MaxVirtualThreads { 12 | 13 | // --enable-preview 14 | 15 | public static void main(String[] args) throws InterruptedException { 16 | 17 | // virtual thread 18 | var threads = 19 | IntStream.range(0, 100) 20 | .mapToObj(index -> 21 | Thread.ofVirtual() 22 | .name("platform-", index) 23 | .unstarted(() -> { 24 | try { 25 | Thread.sleep(2_000); 26 | } catch (InterruptedException e) { 27 | throw new RuntimeException(e); 28 | } 29 | })) 30 | .toList(); 31 | 32 | Instant begin = Instant.now(); 33 | threads.forEach(Thread::start); 34 | for (Thread thread : threads) { 35 | thread.join(); 36 | } 37 | Instant end = Instant.now(); 38 | System.out.println("Duration = " + Duration.between(begin, end)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/E_HowManyVirtualThreads.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.Set; 6 | import java.util.TreeSet; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | import java.util.stream.IntStream; 12 | 13 | public class E_HowManyVirtualThreads { 14 | 15 | // --enable-preview 16 | 17 | public static void main(String[] args) throws InterruptedException { 18 | 19 | Set pools = ConcurrentHashMap.newKeySet(); 20 | Set pThreads = ConcurrentHashMap.newKeySet(); 21 | Pattern pool = Pattern.compile("ForkJoinPool-[\\d?]"); 22 | Pattern worker = Pattern.compile("worker-[\\d?]"); 23 | 24 | var threads = IntStream.range(0, 10_000) 25 | .mapToObj(i -> Thread.ofVirtual() 26 | .unstarted(() -> { 27 | try { 28 | Thread.sleep(2_000); 29 | String name = Thread.currentThread().toString(); 30 | Matcher poolMatcher = pool.matcher(name); 31 | if (poolMatcher.find()) { 32 | pools.add(poolMatcher.group()); 33 | } 34 | Matcher workerMatcher = worker.matcher(name); 35 | if (workerMatcher.find()) { 36 | pThreads.add(workerMatcher.group()); 37 | } 38 | 39 | } catch (InterruptedException e) { 40 | throw new AssertionError(e); 41 | } 42 | })) 43 | .toList(); 44 | Instant begin = Instant.now(); 45 | for (var thread : threads) { 46 | thread.start(); 47 | } 48 | for (var thread : threads) { 49 | thread.join(); 50 | } 51 | Instant end = Instant.now(); 52 | System.out.println("# cores = " + Runtime.getRuntime().availableProcessors()); 53 | System.out.println("Time = " + Duration.between(begin, end)); 54 | System.out.println("Pools"); 55 | pools.forEach(System.out::println); 56 | System.out.println("Platform threads (" + pThreads.size() + ")"); 57 | new TreeSet<>(pThreads).forEach(System.out::println); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/F0_SleepingThreads.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | import java.util.stream.IntStream; 7 | 8 | public class F0_SleepingThreads { 9 | 10 | public static void main(String[] args) throws InterruptedException { 11 | 12 | var counter = new AtomicInteger(); 13 | var threads = IntStream.range(0, 1_000) 14 | .mapToObj(index -> Thread.ofPlatform() 15 | .unstarted(() -> { 16 | try { 17 | if (index == 10) { 18 | System.out.println(Thread.currentThread()); 19 | } 20 | Thread.sleep(1_000); 21 | counter.incrementAndGet(); 22 | } catch (InterruptedException e) { 23 | throw new AssertionError(e); 24 | } 25 | })) 26 | .toList(); 27 | 28 | Instant begin = Instant.now(); 29 | for (var thread : threads) { 30 | thread.start(); 31 | } 32 | for (var thread : threads) { 33 | thread.join(); 34 | } 35 | Instant end = Instant.now(); 36 | System.out.println("# counter = " + counter); 37 | System.out.println("Duration = " + Duration.between(begin, end)); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/F1_SyncedThread.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.stream.IntStream; 6 | 7 | public class F1_SyncedThread { 8 | 9 | private final static Object lock = new Object(); 10 | private static int counter = 0; 11 | 12 | public static void main(String[] args) throws InterruptedException { 13 | 14 | var threads = IntStream.range(0, 1_000) 15 | .mapToObj(index -> Thread.ofVirtual() 16 | .unstarted(() -> { 17 | if (index == 10) { 18 | System.out.println(Thread.currentThread()); 19 | } 20 | synchronized (lock) { 21 | counter++; 22 | } 23 | if (index == 10) { 24 | System.out.println(Thread.currentThread()); 25 | } 26 | synchronized (lock) { 27 | counter++; 28 | } 29 | if (index == 10) { 30 | System.out.println(Thread.currentThread()); 31 | } 32 | synchronized (lock) { 33 | counter++; 34 | } 35 | if (index == 10) { 36 | System.out.println(Thread.currentThread()); 37 | } 38 | })) 39 | .toList(); 40 | 41 | Instant begin = Instant.now(); 42 | for (var thread : threads) { 43 | thread.start(); 44 | } 45 | for (var thread : threads) { 46 | thread.join(); 47 | } 48 | Instant end = Instant.now(); 49 | synchronized (lock) { 50 | System.out.println("# counter = " + counter); 51 | } 52 | System.out.println("Duration = " + Duration.between(begin, end)); 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/F3_LockedThread.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.concurrent.locks.Lock; 6 | import java.util.concurrent.locks.ReentrantLock; 7 | import java.util.stream.IntStream; 8 | 9 | public class F3_LockedThread { 10 | 11 | private final static Lock lock = new ReentrantLock(); 12 | private static int counter = 0; 13 | 14 | public static void main(String[] args) throws InterruptedException { 15 | 16 | var threads = IntStream.range(0, 4_000) 17 | .mapToObj(index -> Thread.ofVirtual() 18 | .unstarted(() -> { 19 | if (index == 10) { 20 | System.out.println(Thread.currentThread()); 21 | } 22 | lock.lock(); 23 | try { 24 | counter++; 25 | } finally { 26 | lock.unlock(); 27 | } 28 | if (index == 10) { 29 | System.out.println(Thread.currentThread()); 30 | } 31 | lock.lock(); 32 | try { 33 | counter++; 34 | } finally { 35 | lock.unlock(); 36 | } 37 | if (index == 10) { 38 | System.out.println(Thread.currentThread()); 39 | } 40 | lock.lock(); 41 | try { 42 | counter++; 43 | } finally { 44 | lock.unlock(); 45 | } 46 | if (index == 10) { 47 | System.out.println(Thread.currentThread()); 48 | } 49 | })) 50 | .toList(); 51 | 52 | Instant begin = Instant.now(); 53 | for (var thread : threads) { 54 | thread.start(); 55 | } 56 | for (var thread : threads) { 57 | thread.join(); 58 | } 59 | Instant end = Instant.now(); 60 | lock.lock(); 61 | try { 62 | System.out.println("# counter = " + counter); 63 | } finally { 64 | lock.unlock(); 65 | } 66 | System.out.println("Duration = " + Duration.between(begin, end)); 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/G0_ContinuationYield.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import jdk.internal.vm.Continuation; 4 | import jdk.internal.vm.ContinuationScope; 5 | 6 | public class G0_ContinuationYield { 7 | 8 | // --enable-preview --add-exports java.base/jdk.internal.vm=ALL-UNNAMED 9 | 10 | public static void main(String[] args) { 11 | 12 | var scope = new ContinuationScope("hello"); 13 | var continuation = 14 | new Continuation( 15 | scope, 16 | () -> { 17 | System.out.println("C1: " + Thread.currentThread()); 18 | Continuation.yield(scope); 19 | System.out.println("C2: " + Thread.currentThread()); 20 | }); 21 | 22 | System.out.println("start"); 23 | continuation.run(); 24 | System.out.println("came back"); 25 | continuation.run(); 26 | System.out.println("Done"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/G1_ContinuationYield.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import jdk.internal.vm.Continuation; 4 | import jdk.internal.vm.ContinuationScope; 5 | 6 | import java.util.ArrayList; 7 | import java.util.stream.IntStream; 8 | 9 | public class G1_ContinuationYield { 10 | 11 | // --enable-preview --add-exports java.base/jdk.internal.vm=ALL-UNNAMED 12 | 13 | public static void main(String[] args) throws InterruptedException { 14 | 15 | 16 | var continuationScopes = 17 | IntStream.range(10, 20) 18 | .mapToObj(index -> new ContinuationScope("Hello-" + index)) 19 | .toList(); 20 | 21 | var continuations = 22 | IntStream.range(10, 20) 23 | .mapToObj(index -> new Continuation(continuationScopes.get(index - 10), 24 | () -> { 25 | System.out.println("A-" + index + " [" + Thread.currentThread() + "]"); 26 | Continuation.yield(continuationScopes.get(index - 10)); 27 | System.out.println("B-" + index + " [" + Thread.currentThread() + "]"); 28 | Continuation.yield(continuationScopes.get(index - 10)); 29 | System.out.println("C-" + index + " [" + Thread.currentThread() + "]"); 30 | })) 31 | .toList(); 32 | 33 | var threads = new ArrayList(); 34 | for (Continuation continuation : continuations) { 35 | threads.add(Thread.ofVirtual().start(continuation::run)); 36 | } 37 | for (Thread thread : threads) { 38 | thread.join(); 39 | } 40 | System.out.println("Step 1"); 41 | 42 | threads.clear(); 43 | for (Continuation continuation : continuations) { 44 | threads.add(Thread.ofVirtual().start(continuation::run)); 45 | } 46 | for (Thread thread : threads) { 47 | thread.join(); 48 | } 49 | System.out.println("Step 2"); 50 | 51 | threads.clear(); 52 | for (Continuation continuation : continuations) { 53 | threads.add(Thread.ofVirtual().start(continuation::run)); 54 | } 55 | for (Thread thread : threads) { 56 | thread.join(); 57 | } 58 | System.out.println("Step 3"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/G2_ContinuationYield.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import jdk.internal.vm.Continuation; 4 | import jdk.internal.vm.ContinuationScope; 5 | 6 | import java.util.concurrent.Callable; 7 | import java.util.concurrent.ExecutionException; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.Future; 10 | import java.util.stream.IntStream; 11 | 12 | public class G2_ContinuationYield { 13 | 14 | // --enable-preview --add-exports java.base/jdk.internal.vm=ALL-UNNAMED 15 | 16 | public static void main(String[] args) throws InterruptedException, ExecutionException { 17 | 18 | 19 | var continuationScopes = 20 | IntStream.range(10, 20) 21 | .mapToObj(index -> new ContinuationScope("Hello-" + index)) 22 | .toList(); 23 | 24 | var continuations = 25 | IntStream.range(10, 20) 26 | .mapToObj(index -> new Continuation(continuationScopes.get(index - 10), 27 | () -> { 28 | System.out.println("A-" + index + " [" + Thread.currentThread() + "]"); 29 | Continuation.yield(continuationScopes.get(index - 10)); 30 | System.out.println("B-" + index + " [" + Thread.currentThread() + "]"); 31 | Continuation.yield(continuationScopes.get(index - 10)); 32 | System.out.println("C-" + index + " [" + Thread.currentThread() + "]"); 33 | })) 34 | .toList(); 35 | 36 | var callables = 37 | continuations.stream() 38 | .>map(continuation -> () -> { 39 | continuation.run(); 40 | continuation.run(); 41 | continuation.run(); 42 | return true; 43 | }) 44 | .toList(); 45 | 46 | try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { 47 | var futures = executor.invokeAll(callables); 48 | for (Future future : futures) { 49 | future.get(); 50 | } 51 | System.out.println("Ok"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/G3_ContinuationYield.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import jdk.internal.vm.Continuation; 4 | import jdk.internal.vm.ContinuationScope; 5 | 6 | import java.util.concurrent.Callable; 7 | import java.util.concurrent.ExecutionException; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.Future; 10 | import java.util.stream.IntStream; 11 | 12 | public class G3_ContinuationYield { 13 | 14 | // --enable-preview --add-exports java.base/jdk.internal.vm=ALL-UNNAMED 15 | 16 | public static void main(String[] args) throws InterruptedException, ExecutionException { 17 | 18 | 19 | var continuationScopes = 20 | IntStream.range(10, 20) 21 | .mapToObj(index -> new ContinuationScope("Hello-" + index)) 22 | .toList(); 23 | 24 | var continuations = 25 | IntStream.range(10, 20) 26 | .mapToObj(index -> new Continuation(continuationScopes.get(index - 10), 27 | () -> { 28 | System.out.println("A-" + index + " [" + Thread.currentThread() + "]"); 29 | Continuation.yield(continuationScopes.get(index - 10)); 30 | System.out.println("B-" + index + " [" + Thread.currentThread() + "]"); 31 | Continuation.yield(continuationScopes.get(index - 10)); 32 | System.out.println("C-" + index + " [" + Thread.currentThread() + "]"); 33 | })) 34 | .toList(); 35 | 36 | var callables = 37 | continuations.stream() 38 | .>map(continuation -> () -> { 39 | continuation.run(); 40 | return true; 41 | }) 42 | .toList(); 43 | 44 | try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { 45 | var futures = executor.invokeAll(callables); 46 | for (Future future : futures) { 47 | future.get(); 48 | } 49 | System.out.println("Step 1"); 50 | futures = executor.invokeAll(callables); 51 | for (Future future : futures) { 52 | future.get(); 53 | } 54 | System.out.println("Step 2"); 55 | futures = executor.invokeAll(callables); 56 | for (Future future : futures) { 57 | future.get(); 58 | } 59 | System.out.println("Step 3"); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/H0_ExtentLocal.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import jdk.incubator.concurrent.ExtentLocal; 4 | 5 | public class H0_ExtentLocal { 6 | 7 | // --enable-preview --add-modules jdk.incubator.concurrent --add-exports java.base/jdk.internal.vm=ALL-UNNAMED 8 | 9 | public static void main(String[] args) throws InterruptedException { 10 | 11 | ExtentLocal KEY = ExtentLocal.newInstance(); 12 | 13 | Runnable task = () -> System.out.println("Key is = " + KEY.get()); 14 | 15 | Runnable taskWithScopeLocalA = () -> ExtentLocal.where(KEY, "Value A").run(task); 16 | Runnable taskWithScopeLocalB = () -> ExtentLocal.where(KEY, "Value B").run(task); 17 | 18 | taskWithScopeLocalA.run(); 19 | taskWithScopeLocalB.run(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/H1_ExtentLocal_ContinuationYield.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import jdk.incubator.concurrent.ExtentLocal; 4 | import jdk.internal.vm.Continuation; 5 | import jdk.internal.vm.ContinuationScope; 6 | 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.Future; 11 | import java.util.stream.IntStream; 12 | 13 | public class H1_ExtentLocal_ContinuationYield { 14 | 15 | // --enable-preview --add-modules jdk.incubator.concurrent --add-exports java.base/jdk.internal.vm=ALL-UNNAMED 16 | 17 | public static void main(String[] args) throws InterruptedException, ExecutionException { 18 | 19 | ExtentLocal KEY = ExtentLocal.newInstance(); 20 | 21 | var continuationScopes = 22 | IntStream.range(10, 20) 23 | .mapToObj(index -> new ContinuationScope("Hello-" + index)) 24 | .toList(); 25 | 26 | Runnable task = () -> { 27 | int key = KEY.get(); 28 | System.out.println("A-" + key + " [" + Thread.currentThread() + "]"); 29 | Continuation.yield(continuationScopes.get(key - 10)); 30 | System.out.println("B-" + key + " [" + Thread.currentThread() + "]"); 31 | Continuation.yield(continuationScopes.get(key - 10)); 32 | System.out.println("C-" + key + " [" + Thread.currentThread() + "]"); 33 | }; 34 | 35 | var continuations = 36 | IntStream.range(10, 20) 37 | .mapToObj(index -> new Continuation(continuationScopes.get(index - 10), 38 | () -> ExtentLocal.where(KEY, index).run(task))) 39 | .toList(); 40 | 41 | var callables = 42 | continuations.stream() 43 | .>map(continuation -> () -> { 44 | continuation.run(); 45 | continuation.run(); 46 | continuation.run(); 47 | return true; 48 | }) 49 | .toList(); 50 | 51 | try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { 52 | var futures = executor.invokeAll(callables); 53 | for (Future future : futures) { 54 | future.get(); 55 | } 56 | System.out.println("Ok"); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/H2_ExtentLocal_ContinuationYield.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | import jdk.incubator.concurrent.ExtentLocal; 4 | import jdk.internal.vm.Continuation; 5 | import jdk.internal.vm.ContinuationScope; 6 | 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.Future; 11 | import java.util.stream.IntStream; 12 | 13 | public class H2_ExtentLocal_ContinuationYield { 14 | 15 | // --enable-preview --add-modules jdk.incubator.concurrent --add-exports java.base/jdk.internal.vm=ALL-UNNAMED 16 | 17 | public static void main(String[] args) throws InterruptedException, ExecutionException { 18 | 19 | ExtentLocal KEY = ExtentLocal.newInstance(); 20 | 21 | var continuationScopes = 22 | IntStream.range(10, 20) 23 | .mapToObj(index -> new ContinuationScope("Hello-" + index)) 24 | .toList(); 25 | 26 | Runnable task = () -> { 27 | int key = KEY.get(); 28 | System.out.println("A-" + key + " [" + Thread.currentThread() + "]"); 29 | Continuation.yield(continuationScopes.get(key - 10)); 30 | System.out.println("B-" + key + " [" + Thread.currentThread() + "]"); 31 | Continuation.yield(continuationScopes.get(key - 10)); 32 | System.out.println("C-" + key + " [" + Thread.currentThread() + "]"); 33 | }; 34 | 35 | var continuations = 36 | IntStream.range(10, 20) 37 | .mapToObj(index -> new Continuation(continuationScopes.get(index - 10), 38 | () -> ExtentLocal.where(KEY, index).run(task))) 39 | .toList(); 40 | 41 | var callables = 42 | continuations.stream() 43 | .>map(continuation -> () -> { 44 | continuation.run(); 45 | return true; 46 | }) 47 | .toList(); 48 | 49 | try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { 50 | var futures = executor.invokeAll(callables); 51 | for (Future future : futures) { 52 | future.get(); 53 | } 54 | System.out.println("Step 1"); 55 | futures = executor.invokeAll(callables); 56 | for (Future future : futures) { 57 | future.get(); 58 | } 59 | System.out.println("Step 2"); 60 | futures = executor.invokeAll(callables); 61 | for (Future future : futures) { 62 | future.get(); 63 | } 64 | System.out.println("Step 3"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/threads/Main.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.threads; 2 | 3 | public class Main { 4 | 5 | public static void main(String[] args) { 6 | 7 | String version = System.getProperty("java.version"); 8 | System.out.println("version = " + version); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/travelpage/TravelPageExample.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.travelpage; 2 | 3 | import jdk.incubator.concurrent.StructuredTaskScope; 4 | import org.paumard.loom.travelpage.model.PageComponent; 5 | import org.paumard.loom.travelpage.model.Quotation; 6 | import org.paumard.loom.travelpage.model.TravelPage; 7 | import org.paumard.loom.travelpage.model.Weather; 8 | 9 | import java.time.temporal.ChronoUnit; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.Future; 12 | 13 | public class TravelPageExample { 14 | 15 | // Changing the following to SECONDS will give you enough time to 16 | // dump the threads in a JSON file, with this command: 17 | // jcmd Thread.dump_to_file -format=json 18 | public static final ChronoUnit CHRONO_UNIT = ChronoUnit.MILLIS; 19 | 20 | private static class TravelPageScope extends StructuredTaskScope { 21 | 22 | private volatile Quotation quotation; 23 | private volatile Weather weather = Weather.UNKNOWN; 24 | private volatile Quotation.QuotationException exception; 25 | 26 | @Override 27 | protected void handleComplete(Future future) { 28 | switch (future.state()) { 29 | case RUNNING -> throw new IllegalStateException("Task is still running"); 30 | case SUCCESS -> { 31 | switch (future.resultNow()) { 32 | case Quotation quotation -> this.quotation = quotation; 33 | case Weather weather -> this.weather = weather; 34 | } 35 | } 36 | case FAILED -> { 37 | switch (future.exceptionNow()) { 38 | case Quotation.QuotationException e -> this.exception = e; 39 | default -> throw new RuntimeException(future.exceptionNow()); 40 | } 41 | } 42 | case CANCELLED -> { 43 | } 44 | } 45 | } 46 | 47 | public TravelPage travelPage() { 48 | if (this.quotation != null) { 49 | return new TravelPage(this.quotation, this.weather); 50 | } else { 51 | throw exception; 52 | } 53 | } 54 | } 55 | 56 | // --enable-preview --add-modules jdk.incubator.concurrent --add-exports java.base/jdk.internal.vm=ALL-UNNAMED 57 | 58 | public static void main(String[] args) throws InterruptedException, ExecutionException { 59 | 60 | var pid = ProcessHandle.current().pid(); 61 | System.out.println("pid = " + pid); 62 | 63 | try (var scope = new TravelPageScope()) { 64 | 65 | scope.fork(Weather::readWeather); 66 | scope.fork(Quotation::readQuotation); 67 | 68 | scope.join(); 69 | 70 | TravelPage travelPage = scope.travelPage(); 71 | System.out.println("Travel page = " + travelPage); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/travelpage/model/PageComponent.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.travelpage.model; 2 | 3 | public sealed interface PageComponent 4 | permits Weather, Quotation { 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/travelpage/model/Quotation.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.travelpage.model; 2 | 3 | import jdk.incubator.concurrent.StructuredTaskScope; 4 | import org.paumard.loom.travelpage.TravelPageExample; 5 | 6 | import java.time.Duration; 7 | import java.time.Instant; 8 | import java.util.Collection; 9 | import java.util.Comparator; 10 | import java.util.Random; 11 | import java.util.concurrent.ConcurrentLinkedQueue; 12 | import java.util.concurrent.Future; 13 | import java.util.concurrent.TimeoutException; 14 | 15 | public record Quotation(String agency, int quotation) implements PageComponent { 16 | 17 | public static class QuotationException extends RuntimeException { 18 | 19 | } 20 | 21 | private static class QuotationScope extends StructuredTaskScope { 22 | private final Collection quotations = new ConcurrentLinkedQueue<>(); 23 | private final Collection exceptions = new ConcurrentLinkedQueue<>(); 24 | 25 | @Override 26 | protected void handleComplete(Future future) { 27 | switch (future.state()) { 28 | case RUNNING -> throw new IllegalStateException("Task is still running"); 29 | case SUCCESS -> this.quotations.add(future.resultNow()); 30 | case FAILED -> this.exceptions.add(future.exceptionNow()); 31 | case CANCELLED -> { 32 | } 33 | } 34 | } 35 | 36 | @Override 37 | public QuotationScope joinUntil(Instant deadline) throws InterruptedException { 38 | 39 | try { 40 | super.joinUntil(deadline); 41 | } catch (TimeoutException e) { 42 | this.shutdown(); 43 | throw new RuntimeException(e); 44 | } 45 | 46 | return this; 47 | } 48 | 49 | public QuotationException exceptions() { 50 | QuotationException exception = new QuotationException(); 51 | exceptions.forEach(exception::addSuppressed); 52 | return exception; 53 | } 54 | 55 | public Quotation bestQuotation() { 56 | return quotations.stream() 57 | .min(Comparator.comparing(Quotation::quotation)) 58 | .orElseThrow(this::exceptions); 59 | } 60 | } 61 | 62 | public static Quotation readQuotation() throws InterruptedException { 63 | 64 | Random random = new Random(); 65 | 66 | try (var scope = new QuotationScope()) { 67 | 68 | scope.fork(() -> { 69 | Thread.sleep(Duration.of(random.nextInt(30, 110), TravelPageExample.CHRONO_UNIT)); 70 | return new Quotation("Agency A", random.nextInt(90, 130)); 71 | }); 72 | scope.fork(() -> { 73 | Thread.sleep(Duration.of(random.nextInt(40, 120), TravelPageExample.CHRONO_UNIT)); 74 | return new Quotation("Agency B", random.nextInt(90, 120)); 75 | }); 76 | scope.fork(() -> { 77 | Thread.sleep(Duration.of(random.nextInt(20, 130), TravelPageExample.CHRONO_UNIT)); 78 | return new Quotation("Agency C", random.nextInt(100, 110)); 79 | }); 80 | 81 | scope.joinUntil(Instant.now().plus(120, TravelPageExample.CHRONO_UNIT)); 82 | 83 | return scope.bestQuotation(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/travelpage/model/TravelPage.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.travelpage.model; 2 | 3 | public record TravelPage(Quotation quotation, Weather weather) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/org/paumard/loom/travelpage/model/Weather.java: -------------------------------------------------------------------------------- 1 | package org.paumard.loom.travelpage.model; 2 | 3 | import jdk.incubator.concurrent.StructuredTaskScope; 4 | import org.paumard.loom.travelpage.TravelPageExample; 5 | 6 | import java.time.Duration; 7 | import java.time.Instant; 8 | import java.util.Random; 9 | import java.util.concurrent.Callable; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.Future; 12 | import java.util.concurrent.TimeoutException; 13 | 14 | public record Weather(String agency, String weather) implements PageComponent { 15 | 16 | public static final Weather UNKNOWN = new Weather("", "Mostly Sunny"); 17 | 18 | private static class WeatherScope implements AutoCloseable { 19 | 20 | private StructuredTaskScope.ShutdownOnSuccess scope = 21 | new StructuredTaskScope.ShutdownOnSuccess<>(); 22 | private boolean timeout = false; 23 | 24 | public WeatherScope joinUntil(Instant deadline) throws InterruptedException { 25 | try { 26 | scope.joinUntil(deadline); 27 | } catch (TimeoutException e) { 28 | scope.shutdown(); 29 | this.timeout = true; 30 | } 31 | return this; 32 | } 33 | 34 | public Future fork(Callable task) { 35 | return scope.fork(task); 36 | } 37 | 38 | @Override 39 | public void close() { 40 | scope.close(); 41 | } 42 | 43 | public Weather getWeather() throws ExecutionException { 44 | if (!timeout) { 45 | return this.scope.result(); 46 | } else { 47 | return Weather.UNKNOWN; 48 | } 49 | } 50 | } 51 | 52 | public static Weather readWeather() throws InterruptedException, ExecutionException { 53 | 54 | Random random = new Random(); 55 | 56 | try (var scope = new WeatherScope()) { 57 | 58 | scope.fork(() -> { 59 | Thread.sleep(Duration.of(random.nextInt(30, 110), TravelPageExample.CHRONO_UNIT)); 60 | return new Weather("WA", "Sunny"); 61 | }); 62 | scope.fork(() -> { 63 | Thread.sleep(Duration.of(random.nextInt(20, 90), TravelPageExample.CHRONO_UNIT)); 64 | return new Weather("WB", "Sunny"); 65 | }); 66 | scope.fork(() -> { 67 | Thread.sleep(Duration.of(random.nextInt(10, 120), TravelPageExample.CHRONO_UNIT)); 68 | return new Weather("WC", "Sunny"); 69 | }); 70 | 71 | scope.joinUntil(Instant.now().plus(100, TravelPageExample.CHRONO_UNIT)); 72 | 73 | return scope.getWeather(); 74 | } 75 | } 76 | } 77 | --------------------------------------------------------------------------------