ããã¯ãªã«ãããããŠæžãããã®ïŒ
ååã®ãšã³ããªãŒã§ãJEP 444ïŒVirtual ThreadsïŒã«ã€ããŠæžããŸããã
Java 21で正式版になったJEP 444(Virtual Threads)に関するAPIを試す - CLOVER🍀
ãã®æã«ã¯èžã¿èŸŒãŸãªãã£ããã¹ã¬ãããŸããã®æåãã¹ã¬ãããã³ããªã©ã確èªããŠã¿ãããšæããŸãã
JEP 444ïŒVirtual ThreadsïŒã«ã€ããŠ
Virtual Threadsã«ã€ããŠã¯ããããããŠã¯èª¬æããŸããã
ãã¡ãã®ãšã³ããªãŒãåç §ããšããããšã§ã
Java 21で正式版になったJEP 444(Virtual Threads)に関するAPIを試す - CLOVER🍀
ä»åã¯ãVirtual Threadsã®äžã§ã以äžã®ç¹ã«çç®ããŠèŠãŠããããšæããŸãã
- ä»®æ³ã¹ã¬ããã¯ãã©ãããã©ãŒã ã¹ã¬ãããããŠã³ãããŠé§åããç¶æ³ã«ãã£ãŠã¢ã³ããŠã³ãããã
- ããŠã³ãïŒã¢ã³ããŠã³ã
- IOãªã©ã®ãããã¯æäœã§ã¢ã³ããŠã³ãããã
ReentrantLock
ã§ã¢ã³ããŠã³ããããsynchronized
ãããã¯ïŒã¡ãœããïŒã§ã¯ã¢ã³ããŠã³ãã§ããªã- CPUããŠã³ããªåŠçãšã¯çžæ§ãæªã
- æ°ãã圢åŒã®ã¹ã¬ãããã³ã
ãªããä»®æ³ã¹ã¬ãããã¢ã³ããŠã³ãã§ããªãç¶æ
ã®ããšãpinningïŒãã³çãïŒãšãããŸããpinningïŒãã³çãïŒãçºçããæã«
ã¹ã¿ãã¯ãã¬ãŒã¹ãåºåããæ¹æ³ãããã®ã§ãããããã¯ãã¡ãã«æžããŸããã
JEP 444(Virtual Threads)のpinning(ピン留め)をシステムプロパティjdk.tracePinnedThreadsによるスタックトレースの出力で確認する - CLOVER🍀
ãé¡
JDKã®HttpServer
ã䜿ã£ãŠãVirtual Threadsã䜿ã£ãç°¡åãªHTTPãµãŒããŒãæžããŠã¿ãŸãã
HttpServer (Java SE 21 & JDK 21)
äœæããHTTPãµãŒããŒã«å¯ŸããŠããããã確èªããŠãã£ãŠã¿ãããšæããŸãããŸããæåŸã«HTTPã¯ã©ã€ã¢ã³ãã䜿ã£ãŠãã¹ãã
ããŠã¿ãŸãã
ç°å¢
ä»åã®ç°å¢ã¯ããã¡ãã
$ java --version openjdk 21.0.1 2023-10-17 OpenJDK Runtime Environment (build 21.0.1+12-Ubuntu-222.04) OpenJDK 64-Bit Server VM (build 21.0.1+12-Ubuntu-222.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.1, vendor: Private Build, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.15.0-91-generic", arch: "amd64", family: "unix"
CPUã¯8ã€ãããŸãã
$ cat /proc/cpuinfo | grep processor | wc -l 8
æºå
MavenäŸåé¢ä¿ãªã©ã¯ãã¡ãã
<properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.24.2</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> <configuration> <forkCount>2</forkCount> </configuration> </plugin> </plugins> </build>
JDKã®HttpServer
ã䜿ãã ããªãç¹ã«äŸåé¢ä¿ã¯èŠããªãã®ã§ããããã¹ãçšã«JUnitãšAssertJãå
¥ããŠããŸãã
Maven Surefire Pluginã«forkCount
ãèšå®ããŠããã®ã¯ããã¹ãã¯ã©ã¹ããšã«ã·ã¹ãã ããããã£ããªã»ãããããããã§ãã
â»ãã¹ãå
ã§äœ¿çšããŠããŸãããããã¯åŸè¿°
HTTPãµãŒããŒãæžã
ããã§ã¯ããé¡ãšãªãHTTPãµãŒããŒãæžããŠã¿ãŸãã
src/main/java/org/littlewings/virtualthreads/SimpleHttpServer.java
package org.littlewings.virtualthreads; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; public class SimpleHttpServer { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss"); private HttpServer httpServer; SimpleHttpServer(HttpServer httpServer) { this.httpServer = httpServer; } public static void main(String... args) { String host; int port; if (args.length > 1) { host = args[0]; port = Integer.parseInt(args[1]); } else if (args.length > 0) { host = "localhost"; port = Integer.parseInt(args[0]); } else { host = "localhost"; port = 8080; } SimpleHttpServer simpleHttpServer = SimpleHttpServer.create(host, port); simpleHttpServer.start(); Runtime.getRuntime().addShutdownHook(Thread.ofPlatform().unstarted(simpleHttpServer::stop)); } private static void log(String message) { Thread currentThread = Thread.currentThread(); String threadName = currentThread.getName(); System.out.printf("[%s] - %s - %s%n", LocalDateTime.now().format(FORMATTER), threadName, message); } public static SimpleHttpServer create(String host, int port) { try { HttpServer httpServer = HttpServer.create(new InetSocketAddress(host, port), 0); log(String.format("jdk.virtualThreadScheduler.parallelism = %s", System.getProperty("jdk.virtualThreadScheduler.parallelism", ""))); log(String.format("jdk.virtualThreadScheduler.maxPoolSize = %s", System.getProperty("jdk.virtualThreadScheduler.maxPoolSize", ""))); // httpServer.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); httpServer.setExecutor(Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("handler-", 1).factory())); httpServer.createContext("/", createHandler()); return new SimpleHttpServer(httpServer); } catch (IOException e) { throw new UncheckedIOException(e); } } public static SimpleHttpServer create(int port) { return create("localhost", port); } static HttpHandler createHandler() { ReentrantLock lock = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); Object synchronizedLockObject = new Object(); Object synchronizedLockObject2 = new Object(); return httpExchange -> { URI requestUri = httpExchange.getRequestURI(); String method = httpExchange.getRequestMethod(); String requestPath = requestUri.getPath(); log(String.format("access[%s:%s] start", method, requestPath)); Consumer<String> writeResponse = responseString -> { byte[] binary = responseString.getBytes(StandardCharsets.UTF_8); try { httpExchange.sendResponseHeaders(200, binary.length); try (OutputStream os = httpExchange.getResponseBody()) { os.write(binary); } } catch (IOException e) { throw new UncheckedIOException(e); } }; Duration sleepTime = Duration.ofSeconds(3L); switch (requestPath) { case "/sleep" -> { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("sleep."); } case "/heavy" -> { long startTime = System.currentTimeMillis(); while (true) { for (int i = 0; i < 100000; i++) { // loop } long elapsedTime = System.currentTimeMillis() - startTime; if (elapsedTime > sleepTime.toMillis()) { break; } } writeResponse.accept("heavy."); } case "/lock" -> { try { lock.lock(); TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } finally { lock.unlock(); } writeResponse.accept("lock."); } case "/lock2" -> { try { lock2.lock(); TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } finally { lock2.unlock(); } writeResponse.accept("lock2."); } case "/synchronized-lock" -> { synchronized (synchronizedLockObject) { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("synchronized lock."); } } case "/synchronized-lock2" -> { synchronized (synchronizedLockObject2) { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("synchronized lock2."); } } default -> writeResponse.accept("Hello World."); } log(String.format("access[%s:%s] end", method, requestPath)); }; } public void start() { httpServer.start(); log(String.format("simple http server[%s:%d], started.", httpServer.getAddress().getHostString(), httpServer.getAddress().getPort())); } public void stop() { httpServer.stop(1); log(String.format("simple http server[%s:%d], shutdown.", httpServer.getAddress().getHostString(), httpServer.getAddress().getPort())); } }
ãªãããŸããŸãã®ããªã¥ãŒã ã«ãªããŸããâŠã
HttpServer
ã®ã€ã³ã¹ã¿ã³ã¹ãæ§ç¯ããŠããéšåã
public static SimpleHttpServer create(String host, int port) { try { HttpServer httpServer = HttpServer.create(new InetSocketAddress(host, port), 0); log(String.format("jdk.virtualThreadScheduler.parallelism = %s", System.getProperty("jdk.virtualThreadScheduler.parallelism", ""))); log(String.format("jdk.virtualThreadScheduler.maxPoolSize = %s", System.getProperty("jdk.virtualThreadScheduler.maxPoolSize", ""))); // httpServer.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); httpServer.setExecutor(Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("handler-", 1).factory())); httpServer.createContext("/", createHandler()); return new SimpleHttpServer(httpServer); } catch (IOException e) { throw new UncheckedIOException(e); } }
ã·ã¹ãã ããããã£jdk.virtualThreadScheduler.parallelism
ãšjdk.virtualThreadScheduler.maxPoolSize
ã¯ä»åã¡ãã£ãšãã€ã³ãã«
ãªãã®ã§ããã°åºåããããã«ããŠããŸãã
HTTPãµãŒããŒã¯Virtual Threadsã§åããããã«ããŠããŸãã
// httpServer.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); httpServer.setExecutor(Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("handler-", 1).factory()));
åçŽã«Executors#newVirtualThreadPerTaskExecutor
ã§ãããã£ãã®ã§ããããã¡ãã§äœæãããšã¹ã¬ããåã空ã«ãªããããªã®ã§
ThreadFactory
ãäœãåã«name
ãæå®ããããã«ããŠããŸãããããããšã第2åŒæ°ã§äžããå€ãã€ã³ã¯ãªã¡ã³ããããŠãããŸãã
ãã°åºåçšã®ã¡ãœããã¯ãã¡ãã§ãã¢ã¯ã»ã¹æã«ã¹ã¬ããåããããããã«ããŠããŸãã
private static void log(String message) { Thread currentThread = Thread.currentThread(); String threadName = currentThread.getName(); System.out.printf("[%s] - %s - %s%n", LocalDateTime.now().format(FORMATTER), threadName, message); }
ãªã¯ãšã¹ããåãä»ããåŸã®åŠçãè¡ãã¡ãœããã¯ãã¡ãã
static HttpHandler createHandler() { ReentrantLock lock = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); Object synchronizedLockObject = new Object(); Object synchronizedLockObject2 = new Object(); return httpExchange -> { URI requestUri = httpExchange.getRequestURI(); String method = httpExchange.getRequestMethod(); String requestPath = requestUri.getPath(); log(String.format("access[%s:%s] start", method, requestPath)); ãçç¥ã Duration sleepTime = Duration.ofSeconds(3L); switch (requestPath) { // ã¢ã¯ã»ã¹ãã¹ããšã®åŠç } log(String.format("access[%s:%s] end", method, requestPath)); }; }
ã¢ã¯ã»ã¹ãã¹ã«å¿ããŠã以äžã®5çš®é¡ã®åŠçãè¡ããŸãã
/sleep
⊠æå®ããç§æ°ã ãTimeUnit#sleep
ã§ã¹ãªãŒãïŒãããã¯æäœã®ä»£ããïŒ/heavy
⊠æå®ããç§æ°ã ãã«ãŒãïŒCPUãæ¶è²»ããåŠçïŒ/lock
ã/lock2
⊠ããããç°ãªãReentrantLock
ã®ã€ã³ã¹ã¿ã³ã¹ã䜿ã£ãŠããã¯ãååŸããæå®ããç§æ°ã ãTimeUnit#sleep
ã§ã¹ãªãŒã/synchronized-lock
ã/synchronized-lock2
⊠ããããç°ãªãã€ã³ã¹ã¿ã³ã¹ã«å¯ŸããŠsynchronized
ã§ããã¯ãååŸããæå®ããç§æ°ã ãTimeUnit#sleep
ã§ã¹ãªãŒã- ãã以å€ã®ã㹠⊠å³åº§ã«
Hello World.
ãè¿ã
ã¢ã¯ã»ã¹ãã¹ããšã®åŠçã¯ãcase
ã«ãªã£ãŠããã®ã§ããããæžããŠãããŸãã
/sleep
ã
case "/sleep" -> { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("sleep."); }
/heavy
ã
case "/heavy" -> { long startTime = System.currentTimeMillis(); while (true) { for (int i = 0; i < 100000; i++) { // loop } long elapsedTime = System.currentTimeMillis() - startTime; if (elapsedTime > sleepTime.toMillis()) { break; } } writeResponse.accept("heavy."); }
/lock
ã/lock2
ã
case "/lock" -> { try { lock.lock(); TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } finally { lock.unlock(); } writeResponse.accept("lock."); } case "/lock2" -> { try { lock2.lock(); TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } finally { lock2.unlock(); } writeResponse.accept("lock2."); }
/synchronized-lock
ã/synchronized-lock2
ã
case "/synchronized-lock" -> { synchronized (synchronizedLockObject) { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("synchronized lock."); } } case "/synchronized-lock2" -> { synchronized (synchronizedLockObject2) { try { TimeUnit.SECONDS.sleep(sleepTime.toSeconds()); } catch (InterruptedException e) { // ignore } writeResponse.accept("synchronized lock2."); } }
ããããåŸ ã€æéã¯3ç§ã«ããŠãããŸãã
Duration sleepTime = Duration.ofSeconds(3L);
ãã以å€ã
default -> writeResponse.accept("Hello World.");
ãŸãããã€ã³ãããã¢ãã¬ã¹ãããŒãã¯ã³ãã³ãã©ã€ã³åŒæ°ãã€ã³ã¹ã¿ã³ã¹äœææã«æå®ã§ããããã«ããŠããŸãã
public static void main(String... args) { String host; int port; if (args.length > 1) { host = args[0]; port = Integer.parseInt(args[1]); } else if (args.length > 0) { host = "localhost"; port = Integer.parseInt(args[0]); } else { host = "localhost"; port = 8080; } SimpleHttpServer simpleHttpServer = SimpleHttpServer.create(host, port); simpleHttpServer.start(); Runtime.getRuntime().addShutdownHook(Thread.ofPlatform().unstarted(simpleHttpServer::stop)); } ãçç¥ã public static SimpleHttpServer create(String host, int port) { try { HttpServer httpServer = HttpServer.create(new InetSocketAddress(host, port), 0); log(String.format("jdk.virtualThreadScheduler.parallelism = %s", System.getProperty("jdk.virtualThreadScheduler.parallelism", ""))); log(String.format("jdk.virtualThreadScheduler.maxPoolSize = %s", System.getProperty("jdk.virtualThreadScheduler.maxPoolSize", ""))); // httpServer.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); httpServer.setExecutor(Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("handler-", 1).factory())); httpServer.createContext("/", createHandler()); return new SimpleHttpServer(httpServer); } catch (IOException e) { throw new UncheckedIOException(e); } } public static SimpleHttpServer create(int port) { return create("localhost", port); }
äž»é¡ã¯ãã®ããããªã®ã§ãä»ã®éšåã®èª¬æã¯ç«¯æããŸãã
åãããŠæåãèŠãŠã¿ã
ãã«ãããŠåãããŠã¿ãŸãã
ãã«ãã
$ mvn compile
å®è¡ã§ããã以äžã®ããã«ããŠã·ã¹ãã ããããã£jdk.virtualThreadScheduler.parallelism
ãšjdk.virtualThreadScheduler.maxPoolSize
ã
1ã«ããŠäžŠå床ããã³å©çšå¯èœãªãã©ãããã©ãŒã ã¹ã¬ããã®æ°ã1ã«ããŠããŸãã
$ java \ -Djdk.virtualThreadScheduler.parallelism=1 \ -Djdk.virtualThreadScheduler.maxPoolSize=1 \ -cp target/classes \ org.littlewings.virtualthreads.SimpleHttpServer
ä»®æ³ã¹ã¬ãããã¢ã³ããŠã³ãã§ããªãåŠçã«å
¥ã£ãæã«ãä»ã®ãã©ãããã©ãŒã ã¹ã¬ããã䜿ã£ãŠåäœã§ããªãããã«ããããšããã®èšå®ã®
æå³ã§ãã
èµ·åæã®ãã°ã
[2023-12-12 00:12:22] - main - jdk.virtualThreadScheduler.parallelism = 1 [2023-12-12 00:12:22] - main - jdk.virtualThreadScheduler.maxPoolSize = 1 [2023-12-12 00:12:22] - main - simple http server[127.0.0.1:8080], started.
確èªã
$ time curl localhost:8080 Hello World. real 0m0.012s user 0m0.006s sys 0m0.005s
ã¢ã¯ã»ã¹ãããšããããªæãã«ãã°ãåºåãããŸãã
[2023-12-12 00:14:38] - handler-1 - access[GET:/] start [2023-12-12 00:14:38] - handler-1 - access[GET:/] end
ããããã¯ã2ã€ã®ã¿ãŒããã«ã䜿ã£ãŠcurlã³ãã³ãã§ã¢ã¯ã»ã¹ãã€ã€åŠçæéãèŠãŠãããŸããã¢ã¯ã»ã¹ã¯ãåºæ¬çã«åæã«
è¡ã£ãŠããŸãã
TimeUnit#sleep
ã§ã¹ãªãŒãããã/sleep
ã«ã¢ã¯ã»ã¹ã
## ã²ãšã€ç® $ time curl localhost:8080/sleep sleep. real 0m3.014s user 0m0.006s sys 0m0.005s ## 2ã€ç® $ time curl localhost:8080/sleep sleep. real 0m3.013s user 0m0.006s sys 0m0.005s
2ã€ã®ãªã¯ãšã¹ããåŠçããŸããããäž¡æ¹ãšãã»ãŒ3ç§ã§è¿ã£ãŠããŸããã
ã¢ã¯ã»ã¹ãã°äžãåãã§ãã
[2023-12-12 00:17:06] - handler-5 - access[GET:/sleep] start [2023-12-12 00:17:06] - handler-6 - access[GET:/sleep] start [2023-12-12 00:17:09] - handler-5 - access[GET:/sleep] end [2023-12-12 00:17:09] - handler-6 - access[GET:/sleep] end
空ã«ãŒããåããŠCPUãæ¶è²»ãã/heavy
ã
## ã²ãšã€ç® $ time curl localhost:8080/heavy heavy. real 0m3.011s user 0m0.004s sys 0m0.005s ## 2ã€ç® $ time curl localhost:8080/heavy heavy. real 0m5.672s user 0m0.007s sys 0m0.000s
çæ¹ã2åè¿ãæéã«ãªããŸããã2å以äžã«ãªã£ãŠããã®ã¯ãåã¿ãŒããã«ã§ããããã³ãã³ããèµ·åããŠãã®ã§ããã®ã©ã°ã ãš
æããŸãâŠã
ã¢ã¯ã»ã¹ãã°ãèŠããšãçæ¹ãåããŠããéã¯ããã²ãšã€ãé²ããããªããªã£ãŠããã¿ããã§ããã
[2023-12-12 00:19:21] - handler-9 - access[GET:/heavy] start [2023-12-12 00:19:24] - handler-9 - access[GET:/heavy] end [2023-12-12 00:19:24] - handler-10 - access[GET:/heavy] start [2023-12-12 00:19:27] - handler-10 - access[GET:/heavy] end
ReentrantLock
ã䜿ã/lock
ã
## ã²ãšã€ç® $ time curl localhost:8080/lock lock. real 0m3.014s user 0m0.006s sys 0m0.005s ## 2ã€ç® $ time curl localhost:8080/lock lock. real 0m5.706s user 0m0.012s sys 0m0.001s
/heavy
ãšåãããã«æéãçŽ2åã«ãªããŸããããããã¯ããããããã¯ãåã£ãŠããã®ã§ãããªããŸãããã
[2023-12-12 00:21:23] - handler-17 - access[GET:/lock] start [2023-12-12 00:21:23] - handler-18 - access[GET:/lock] start [2023-12-12 00:21:26] - handler-17 - access[GET:/lock] end [2023-12-12 00:21:29] - handler-18 - access[GET:/lock] end
ã§ã¯ãç°ãªãReentrantLock
ã䜿ã/lock
ãš/lock2
ã§ã¯ã©ãã§ãããã
## lock $ time curl localhost:8080/lock lock. real 0m3.012s user 0m0.005s sys 0m0.005s ## lock2 $ time curl localhost:8080/lock2 lock2. real 0m3.011s user 0m0.008s sys 0m0.000s
ããã¯ããŠãã察象ãç°ãªãããã€ã¹ãªãŒãããŠããã®ã¯ãããã¯ããTimeUnit#sleep
ãªã®ã§çæ¹ã2åã®åŠçæéã«ãªããããªããšã¯
ãããŸããã
[2023-12-12 00:23:26] - handler-21 - access[GET:/lock] start [2023-12-12 00:23:27] - handler-22 - access[GET:/lock2] start [2023-12-12 00:23:29] - handler-21 - access[GET:/lock] end [2023-12-12 00:23:30] - handler-22 - access[GET:/lock2] end
synchronized
ã§ããã¯ãåãã/synchronized-lock
ãè©ŠããŠã¿ãŸãã
## ã²ãšã€ç® $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m3.012s user 0m0.004s sys 0m0.007s ## 2ã€ç® $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m5.655s user 0m0.010s sys 0m0.004s
ããã¯ãåã£ãŠããã®ã§ãçæ¹ã¯åãããã®æéãããããŸããã
[2023-12-12 00:25:26] - handler-25 - access[GET:/synchronized-lock] start [2023-12-12 00:25:29] - handler-25 - access[GET:/synchronized-lock] end [2023-12-12 00:25:29] - handler-26 - access[GET:/synchronized-lock] start [2023-12-12 00:25:32] - handler-26 - access[GET:/synchronized-lock] end
ã§ã¯ãå¥ã
ã®ã€ã³ã¹ã¿ã³ã¹ã«å¯ŸããŠããã¯ãåã/synchronized-lock
ãš/synchronized-lock2
ã§è©ŠããŠã¿ãŸãã
## /synchronized-lock $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m3.014s user 0m0.005s sys 0m0.006s ## /synchronized-lock2 $ time curl localhost:8080/synchronized-lock2 synchronized lock2. real 0m5.658s user 0m0.001s sys 0m0.010s
ãã¡ãã¯ãç°ãªãã€ã³ã¹ã¿ã³ã¹ã«å¯ŸããŠããã¯ãååŸããŠããã®ã«çæ¹ã¯åè¿ãæéãããããŸãããããããsynchonized
ãããã¯ã
䜿ã£ãŠãããšã¢ã³ããŠã³ãã§ããªããšããããšãªã®ããªãšæããŸãã
[2023-12-12 00:27:30] - handler-29 - access[GET:/synchronized-lock] start [2023-12-12 00:27:33] - handler-29 - access[GET:/synchronized-lock] end [2023-12-12 00:27:33] - handler-30 - access[GET:/synchronized-lock2] start [2023-12-12 00:27:36] - handler-30 - access[GET:/synchronized-lock2] end
ãšããããšã¯ãå
ã«sychronized
ãããã¯ã®ãããªã¢ã³ããŠã³ãã§ããªããã®ãåãããšãã¢ã³ããŠã³ãå¯èœãªåŠçã§ãåŸ
ããããããšã«
ãªãã¯ãã§ããã
/synchronized-lock
ãš/sleep
ã§è©ŠããŠã¿ãŸãããã
## /synchronized-lock $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m3.011s user 0m0.006s sys 0m0.004s ## /sleep $ time curl localhost:8080/sleep sleep. real 0m5.678s user 0m0.005s sys 0m0.005s
äºæ³éãã®çµæã«ãªããŸããã
ãããŸã§ã§ã以äžã®ç¹ã¯ç¢ºèªã§ããã®ã§ã¯ãªãããªãšæããŸãã
- ä»®æ³ã¹ã¬ããã¯ãã©ãããã©ãŒã ã¹ã¬ãããããŠã³ãããŠé§åããç¶æ³ã«ãã£ãŠã¢ã³ããŠã³ãããã
- ããŠã³ãïŒã¢ã³ããŠã³ã
- IOãªã©ã®ãããã¯æäœã§ã¢ã³ããŠã³ãããã
ReentrantLock
ã§ã¢ã³ããŠã³ããããsynchronized
ãããã¯ïŒã¡ãœããïŒã§ã¯ã¢ã³ããŠã³ãã§ããªã- CPUããŠã³ããªåŠçãšã¯çžæ§ãæªã
ãªããä»®æ³ã¹ã¬ãããã¢ã³ããŠã³ãã§ããªãç¶æ
ã§ããpinningïŒãã³çãïŒãçºçããæã«ã¹ã¿ãã¯ãã¬ãŒã¹ãåºåããæ¹æ³ãããã
ããã¯ãã¡ãã«æžããŠããŸãã
JEP 444(Virtual Threads)のpinning(ピン留め)をシステムプロパティjdk.tracePinnedThreadsによるスタックトレースの出力で確認する - CLOVER🍀
ã§ã¯ã䞊å床ãšãã©ãããã©ãŒã ã¹ã¬ããã®æ°ãå¢ãããšã©ããªãã§ãããããã¢ã³ããŠã³ãã§ããªããªã£ãŠããå¢ãããåãããã¯
åããŠãããããªæ°ãããŸããã
2ã«ããŠè©ŠããŠã¿ãŸãããã
$ java \ -Djdk.virtualThreadScheduler.parallelism=2 \ -Djdk.virtualThreadScheduler.maxPoolSize=2 \ -cp target/classes \ org.littlewings.virtualthreads.SimpleHttpServer [2023-12-12 01:48:01] - main - jdk.virtualThreadScheduler.parallelism = 2 [2023-12-12 01:48:01] - main - jdk.virtualThreadScheduler.maxPoolSize = 2 [2023-12-12 01:48:01] - main - simple http server[127.0.0.1:8080], started.
å ã»ã©ãã¢ã¯ã»ã¹æéãåã«ãªã£ãçµã¿åãããè©ŠããŠã¿ãŸãã
空ã«ãŒããåããŠCPUãæ¶è²»ãã/heavy
ã
## ã²ãšã€ç® $ time curl localhost:8080/heavy heavy. real 0m3.065s user 0m0.006s sys 0m0.000s ## 2ã€ç® $ time curl localhost:8080/heavy heavy. real 0m3.009s user 0m0.007s sys 0m0.000s
2ã€ç®ã®ãªã¯ãšã¹ããã²ãšã€ç®ã®ãªã¯ãšã¹ããåŸ ããªããªããŸãããã
[2023-12-12 01:48:49] - handler-1 - access[GET:/heavy] start [2023-12-12 01:48:49] - handler-2 - access[GET:/heavy] start [2023-12-12 01:48:52] - handler-1 - access[GET:/heavy] end [2023-12-12 01:48:52] - handler-2 - access[GET:/heavy] end
ã§ã¯ãå¥ã
ã®ã€ã³ã¹ã¿ã³ã¹ã«å¯ŸããŠããã¯ãåã/synchronized-lock
ãš/synchronized-lock2
ã
## /synchronized-lock $ time curl localhost:8080/synchronized-lock synchronized lock. real 0m3.010s user 0m0.002s sys 0m0.005s ## /synchronized-lock2 $ time curl localhost:8080/synchronized-lock2 synchronized lock2. real 0m3.009s user 0m0.003s sys 0m0.004s
ãã¡ããååŸåã«ãªããŸãããã
[2023-12-12 01:50:55] - handler-5 - access[GET:/synchronized-lock] start [2023-12-12 01:50:56] - handler-6 - access[GET:/synchronized-lock2] start [2023-12-12 01:50:58] - handler-5 - access[GET:/synchronized-lock] end [2023-12-12 01:50:59] - handler-6 - access[GET:/synchronized-lock2] end
ãšããããã§ãå²ãåœãŠããããã©ãããã©ãŒã ã¹ã¬ãããããã°ãã¡ãã䜿ã£ãŠãããããšã¯ç¢ºèªã§ããŸããã
ãã£ãšããããããäºæ ãã®ãã®ãé¿ããã¹ããªã®ã§ããããã©ãã
æ°ãã圢åŒã®ã¹ã¬ãããã³ããèŠã
次ã¯ã¹ã¬ãããã³ããèŠãŠã¿ãŸãããã
䞊å床ããã³ãã©ãããã©ãŒã ã¹ã¬ããæ°ã1ã«ããŠãHTTPãµãŒããŒãèµ·åãçŽããŸãã
$ java \ -Djdk.virtualThreadScheduler.parallelism=1 \ -Djdk.virtualThreadScheduler.maxPoolSize=1 \ -cp target/classes \ org.littlewings.virtualthreads.SimpleHttpServer [2023-12-12 22:58:34] - main - jdk.virtualThreadScheduler.parallelism = 1 [2023-12-12 22:58:34] - main - jdk.virtualThreadScheduler.maxPoolSize = 1 [2023-12-12 22:58:34] - main - simple http server[127.0.0.1:8080], started.
ãŸãã¯ã¹ãªãŒããããŠããæã«
$ curl localhost:8080/sleep
ã¹ã¬ãããã³ããåã£ãŠã¿ãŸãã
$ jcmd $(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1) Thread.print
éåžžãjcmd [PID] [command]
ãšããæå®ã§ãããPIDã®ååŸã¯ãµãã·ã§ã«ã«ä»»ããŠããŸãâŠã
以éã¯$(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1)
ãšããæå®ã¯ãJavaã¢ããªã±ãŒã·ã§ã³ã®PIDãååŸããŠãããã ããšæã£ãŠ
èŠãŠãã ããã
çµæã
13711: 2023-12-12 22:59:51 Full thread dump OpenJDK 64-Bit Server VM (21.0.1+12-Ubuntu-222.04 mixed mode, sharing): Threads class SMR info: _java_thread_list=0x00007fd224002120, length=15, elements={ 0x00007fd2b0164be0, 0x00007fd2b0166280, 0x00007fd2b0167d30, 0x00007fd2b0169390, 0x00007fd2b016a950, 0x00007fd2b016c4b0, 0x00007fd2b016db90, 0x00007fd2b0183db0, 0x00007fd2b0187880, 0x00007fd2b01cc400, 0x00007fd2b01e41a0, 0x00007fd2b001ce80, 0x00007fd1f8043850, 0x00007fd204012740, 0x00007fd224000fe0 } "Reference Handler" #9 [13873] daemon prio=10 os_prio=0 cpu=0.40ms elapsed=78.23s tid=0x00007fd2b0164be0 nid=13873 waiting on condition [0x00007fd290405000] java.lang.Thread.State: RUNNABLE at java.lang.ref.Reference.waitForReferencePendingList(java.base@21.0.1/Native Method) at java.lang.ref.Reference.processPendingReferences(java.base@21.0.1/Reference.java:246) at java.lang.ref.Reference$ReferenceHandler.run(java.base@21.0.1/Reference.java:208) "Finalizer" #10 [13874] daemon prio=8 os_prio=0 cpu=0.20ms elapsed=78.23s tid=0x00007fd2b0166280 nid=13874 in Object.wait() [0x00007fd290305000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait0(java.base@21.0.1/Native Method) - waiting on <0x0000000717001670> (a java.lang.ref.NativeReferenceQueue$Lock) at java.lang.Object.wait(java.base@21.0.1/Object.java:366) at java.lang.Object.wait(java.base@21.0.1/Object.java:339) at java.lang.ref.NativeReferenceQueue.await(java.base@21.0.1/NativeReferenceQueue.java:48) at java.lang.ref.ReferenceQueue.remove0(java.base@21.0.1/ReferenceQueue.java:158) at java.lang.ref.NativeReferenceQueue.remove(java.base@21.0.1/NativeReferenceQueue.java:89) - locked <0x0000000717001670> (a java.lang.ref.NativeReferenceQueue$Lock) at java.lang.ref.Finalizer$FinalizerThread.run(java.base@21.0.1/Finalizer.java:173) "Signal Dispatcher" #11 [13875] daemon prio=9 os_prio=0 cpu=0.33ms elapsed=78.23s tid=0x00007fd2b0167d30 nid=13875 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Service Thread" #12 [13876] daemon prio=9 os_prio=0 cpu=0.18ms elapsed=78.23s tid=0x00007fd2b0169390 nid=13876 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Monitor Deflation Thread" #13 [13877] daemon prio=9 os_prio=0 cpu=16.01ms elapsed=78.23s tid=0x00007fd2b016a950 nid=13877 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #14 [13878] daemon prio=9 os_prio=0 cpu=79.37ms elapsed=78.23s tid=0x00007fd2b016c4b0 nid=13878 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE No compile task "C1 CompilerThread0" #17 [13879] daemon prio=9 os_prio=0 cpu=147.73ms elapsed=78.23s tid=0x00007fd2b016db90 nid=13879 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE No compile task "Notification Thread" #18 [13880] daemon prio=9 os_prio=0 cpu=0.30ms elapsed=78.06s tid=0x00007fd2b0183db0 nid=13880 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Common-Cleaner" #19 [13881] daemon prio=8 os_prio=0 cpu=0.57ms elapsed=77.93s tid=0x00007fd2b0187880 nid=13881 waiting on condition [0x00007fd23ab2d000] java.lang.Thread.State: TIMED_WAITING (parking) at jdk.internal.misc.Unsafe.park(java.base@21.0.1/Native Method) - parking to wait for <0x0000000717011010> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.1/LockSupport.java:269) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.1/AbstractQueuedSynchronizer.java:1847) at java.lang.ref.ReferenceQueue.await(java.base@21.0.1/ReferenceQueue.java:71) at java.lang.ref.ReferenceQueue.remove0(java.base@21.0.1/ReferenceQueue.java:143) at java.lang.ref.ReferenceQueue.remove(java.base@21.0.1/ReferenceQueue.java:218) at jdk.internal.ref.CleanerImpl.run(java.base@21.0.1/CleanerImpl.java:140) at java.lang.Thread.runWith(java.base@21.0.1/Thread.java:1596) at java.lang.Thread.run(java.base@21.0.1/Thread.java:1583) at jdk.internal.misc.InnocuousThread.run(java.base@21.0.1/InnocuousThread.java:186) "idle-timeout-task" #20 [13882] daemon prio=5 os_prio=0 cpu=1.03ms elapsed=77.68s tid=0x00007fd2b01cc400 nid=13882 in Object.wait() [0x00007fd23aa16000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait0(java.base@21.0.1/Native Method) - waiting on <0x000000071706e308> (a java.util.TaskQueue) at java.lang.Object.wait(java.base@21.0.1/Object.java:366) at java.util.TimerThread.mainLoop(java.base@21.0.1/Timer.java:563) - locked <0x000000071706e308> (a java.util.TaskQueue) at java.util.TimerThread.run(java.base@21.0.1/Timer.java:516) "HTTP-Dispatcher" #21 [13884] prio=5 os_prio=0 cpu=50.94ms elapsed=77.59s tid=0x00007fd2b01e41a0 nid=13884 runnable [0x00007fd23a80b000] java.lang.Thread.State: RUNNABLE at sun.nio.ch.EPoll.wait(java.base@21.0.1/Native Method) at sun.nio.ch.EPollSelectorImpl.doSelect(java.base@21.0.1/EPollSelectorImpl.java:121) at sun.nio.ch.SelectorImpl.lockAndDoSelect(java.base@21.0.1/SelectorImpl.java:130) - locked <0x000000071706bc48> (a sun.nio.ch.Util$2) - locked <0x000000071706b8c0> (a sun.nio.ch.EPollSelectorImpl) at sun.nio.ch.SelectorImpl.select(java.base@21.0.1/SelectorImpl.java:142) at sun.net.httpserver.ServerImpl$Dispatcher.run(jdk.httpserver@21.0.1/ServerImpl.java:474) at java.lang.Thread.runWith(java.base@21.0.1/Thread.java:1596) at java.lang.Thread.run(java.base@21.0.1/Thread.java:1583) "DestroyJavaVM" #23 [13712] prio=5 os_prio=0 cpu=276.12ms elapsed=77.56s tid=0x00007fd2b001ce80 nid=13712 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "ForkJoinPool-1-worker-1" #25 [14092] daemon prio=5 os_prio=0 cpu=25.68ms elapsed=2.32s tid=0x00007fd1f8043850 nid=14092 waiting on condition [0x00007fd23a90b000] java.lang.Thread.State: TIMED_WAITING (parking) at jdk.internal.misc.Unsafe.park(java.base@21.0.1/Native Method) - parking to wait for <0x0000000716f0c408> (a java.util.concurrent.ForkJoinPool) at java.util.concurrent.locks.LockSupport.parkUntil(java.base@21.0.1/LockSupport.java:449) at java.util.concurrent.ForkJoinPool.awaitWork(java.base@21.0.1/ForkJoinPool.java:1891) at java.util.concurrent.ForkJoinPool.runWorker(java.base@21.0.1/ForkJoinPool.java:1809) at java.util.concurrent.ForkJoinWorkerThread.run(java.base@21.0.1/ForkJoinWorkerThread.java:188) "VirtualThread-unparker" #26 [14093] daemon prio=5 os_prio=0 cpu=0.38ms elapsed=2.25s tid=0x00007fd204012740 nid=14093 waiting on condition [0x00007fd23a70b000] java.lang.Thread.State: TIMED_WAITING (parking) at jdk.internal.misc.Unsafe.park(java.base@21.0.1/Native Method) - parking to wait for <0x0000000716f14a70> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.1/LockSupport.java:269) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(java.base@21.0.1/AbstractQueuedSynchronizer.java:1758) at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.1/ScheduledThreadPoolExecutor.java:1182) at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(java.base@21.0.1/ScheduledThreadPoolExecutor.java:899) at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@21.0.1/ThreadPoolExecutor.java:1070) at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@21.0.1/ThreadPoolExecutor.java:1130) at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@21.0.1/ThreadPoolExecutor.java:642) at java.lang.Thread.runWith(java.base@21.0.1/Thread.java:1596) at java.lang.Thread.run(java.base@21.0.1/Thread.java:1583) at jdk.internal.misc.InnocuousThread.run(java.base@21.0.1/InnocuousThread.java:186) "Attach Listener" #27 [14136] daemon prio=9 os_prio=0 cpu=0.30ms elapsed=0.10s tid=0x00007fd224000fe0 nid=14136 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "VM Thread" os_prio=0 cpu=4.86ms elapsed=78.45s tid=0x00007fd2b0157850 nid=13872 runnable "GC Thread#0" os_prio=0 cpu=0.25ms elapsed=79.36s tid=0x00007fd2b0084aa0 nid=13713 runnable "G1 Main Marker" os_prio=0 cpu=0.25ms elapsed=79.36s tid=0x00007fd2b0095950 nid=13714 runnable "G1 Conc#0" os_prio=0 cpu=0.20ms elapsed=79.36s tid=0x00007fd2b0096910 nid=13715 runnable "G1 Refine#0" os_prio=0 cpu=0.12ms elapsed=79.36s tid=0x00007fd2b0122430 nid=13716 runnable "G1 Service" os_prio=0 cpu=4.68ms elapsed=79.36s tid=0x00007fd2b0123400 nid=13717 runnable "VM Periodic Task Thread" os_prio=0 cpu=85.24ms elapsed=78.62s tid=0x00007fd2b013d0f0 nid=13867 waiting on condition JNI global refs: 16, weak refs: 0
TimeUnit#sleep
ã§æ¢ãŸã£ãŠããã¹ã¬ãããããŸããããããããhandler-N
ãšããååã®ã¹ã¬ãããããŸããããã©ããã
Virtual Threadsã¯è¡šç€ºãããªãããã§ãã
Virtual Threadsã®ã¹ã¿ãã¯ãã¬ãŒã¹ãå«ãã«ã¯ãThread.dump_to_file
ã䜿ãããã§ãã
$ jcmd $(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1) help Thread.dump_to_file 13711: Thread.dump_to_file Dump threads, with stack traces, to a file in plain text or JSON format. Impact: Medium: Depends on the number of threads. Syntax : Thread.dump_to_file [options] <filepath> Arguments: filepath : The file path to the output file (STRING, no default value) Options: (options must be specified using the <key> or <key>=<value> syntax) -overwrite : [optional] May overwrite existing file (BOOLEAN, false) -format : [optional] Output format ("plain" or "json") (STRING, plain)
ã¡ãªã¿ã«ããã®ã³ãã³ãã¯jcmdã®ããã¥ã¡ã³ãã«ã¯èŒã£ãŠããªãããã§ãâŠã
ãã1床ã¹ãªãŒããããŠ
$ curl localhost:8080/sleep
ã¹ã¬ãããã³ããååŸãåºå圢åŒã¯ãŸãã¯ããã¹ãïŒplain
ïŒã«ããŠããŸãããŸãããã®ã³ãã³ãã¯åºåçµæããã¡ã€ã«ã«ãªããŸãã
$ jcmd $(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1) Thread.dump_to_file -format=plain thread_dump.txt
ãã¡ã€ã«ãäœæãããŸããã
13711: Created /path/to/thread_dump.txt
äžèº«ãèŠãŠã¿ãŸãã
thread_dump.txt
13711 2023-12-12T14:07:43.882089998Z 21.0.1+12-Ubuntu-222.04 #9 "Reference Handler" java.base/java.lang.ref.Reference.waitForReferencePendingList(Native Method) java.base/java.lang.ref.Reference.processPendingReferences(Reference.java:246) java.base/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:208) #10 "Finalizer" java.base/java.lang.Object.wait0(Native Method) java.base/java.lang.Object.wait(Object.java:366) java.base/java.lang.Object.wait(Object.java:339) java.base/java.lang.ref.NativeReferenceQueue.await(NativeReferenceQueue.java:48) java.base/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:158) java.base/java.lang.ref.NativeReferenceQueue.remove(NativeReferenceQueue.java:89) java.base/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:173) #11 "Signal Dispatcher" #18 "Notification Thread" #19 "Common-Cleaner" java.base/jdk.internal.misc.Unsafe.park(Native Method) java.base/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269) java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1847) java.base/java.lang.ref.ReferenceQueue.await(ReferenceQueue.java:71) java.base/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:143) java.base/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:218) java.base/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140) java.base/java.lang.Thread.run(Thread.java:1583) java.base/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186) #20 "idle-timeout-task" java.base/java.lang.Object.wait0(Native Method) java.base/java.lang.Object.wait(Object.java:366) java.base/java.util.TimerThread.mainLoop(Timer.java:563) java.base/java.util.TimerThread.run(Timer.java:516) #21 "HTTP-Dispatcher" java.base/sun.nio.ch.EPoll.wait(Native Method) java.base/sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:121) java.base/sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:130) java.base/sun.nio.ch.SelectorImpl.select(SelectorImpl.java:142) jdk.httpserver/sun.net.httpserver.ServerImpl$Dispatcher.run(ServerImpl.java:474) java.base/java.lang.Thread.run(Thread.java:1583) #23 "DestroyJavaVM" #27 "Attach Listener" java.base/java.lang.Thread.getStackTrace(Thread.java:2450) java.base/jdk.internal.vm.ThreadDumper.dumpThread(ThreadDumper.java:162) java.base/jdk.internal.vm.ThreadDumper.lambda$dumpThreads$0(ThreadDumper.java:155) java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:310) java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:762) java.base/jdk.internal.vm.ThreadDumper.dumpThreads(ThreadDumper.java:155) java.base/jdk.internal.vm.ThreadDumper.dumpThreads(ThreadDumper.java:151) java.base/jdk.internal.vm.ThreadDumper.dumpThreadsToFile(ThreadDumper.java:117) java.base/jdk.internal.vm.ThreadDumper.dumpThreads(ThreadDumper.java:67) #29 "handler-3" virtual java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:621) java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:793) java.base/java.lang.Thread.sleep(Thread.java:556) java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446) org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:109) jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98) jdk.httpserver/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82) jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101) jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:871) jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98) jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:847) java.base/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314) java.base/java.lang.VirtualThread.run(VirtualThread.java:309) #30 "ForkJoinPool-1-worker-2" java.base/jdk.internal.misc.Unsafe.park(Native Method) java.base/java.util.concurrent.locks.LockSupport.parkUntil(LockSupport.java:449) java.base/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1891) java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1809) java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188) #26 "VirtualThread-unparker" java.base/jdk.internal.misc.Unsafe.park(Native Method) java.base/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269) java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1758) java.base/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182) java.base/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899) java.base/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1070) java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) java.base/java.lang.Thread.run(Thread.java:1583) java.base/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)
ä»åºŠã¯Virtual ThreadsãåºçŸããŸããã
#29 "handler-3" virtual java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:621) java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:793) java.base/java.lang.Thread.sleep(Thread.java:556) java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446) org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:109) jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98) jdk.httpserver/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82) jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101) jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:871) jdk.httpserver/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98) jdk.httpserver/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:847) java.base/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314) java.base/java.lang.VirtualThread.run(VirtualThread.java:309)
Thread.dump_to_file
ã¯format=json
ã§JSON圢åŒã§ãåºåã§ããŸãã
$ jcmd $(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1) Thread.dump_to_file -format=json thread_dump.json
çµæã
thread_dump.json
{ "threadDump": { "processId": "13711", "time": "2023-12-12T14:11:05.724062912Z", "runtimeVersion": "21.0.1+12-Ubuntu-222.04", "threadContainers": [ { "container": "<root>", "parent": null, "owner": null, "threads": [ { "tid": "9", "name": "Reference Handler", "stack": [ "java.base\/java.lang.ref.Reference.waitForReferencePendingList(Native Method)", "java.base\/java.lang.ref.Reference.processPendingReferences(Reference.java:246)", "java.base\/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:208)" ] }, { "tid": "10", "name": "Finalizer", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.lang.Object.wait(Object.java:339)", "java.base\/java.lang.ref.NativeReferenceQueue.await(NativeReferenceQueue.java:48)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:158)", "java.base\/java.lang.ref.NativeReferenceQueue.remove(NativeReferenceQueue.java:89)", "java.base\/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:173)" ] }, { "tid": "11", "name": "Signal Dispatcher", "stack": [ ] }, { "tid": "18", "name": "Notification Thread", "stack": [ ] }, { "tid": "19", "name": "Common-Cleaner", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1847)", "java.base\/java.lang.ref.ReferenceQueue.await(ReferenceQueue.java:71)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:143)", "java.base\/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:218)", "java.base\/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] }, { "tid": "20", "name": "idle-timeout-task", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.util.TimerThread.mainLoop(Timer.java:563)", "java.base\/java.util.TimerThread.run(Timer.java:516)" ] }, { "tid": "21", "name": "HTTP-Dispatcher", "stack": [ "java.base\/sun.nio.ch.EPoll.wait(Native Method)", "java.base\/sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:121)", "java.base\/sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:130)", "java.base\/sun.nio.ch.SelectorImpl.select(SelectorImpl.java:142)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Dispatcher.run(ServerImpl.java:474)", "java.base\/java.lang.Thread.run(Thread.java:1583)" ] }, { "tid": "23", "name": "DestroyJavaVM", "stack": [ ] }, { "tid": "27", "name": "Attach Listener", "stack": [ "java.base\/java.lang.Thread.getStackTrace(Thread.java:2450)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadToJson(ThreadDumper.java:264)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:237)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:201)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToFile(ThreadDumper.java:115)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:84)" ] } ], "threadCount": "9" }, { "container": "ForkJoinPool.commonPool\/jdk.internal.vm.SharedThreadContainer@7350b626", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPoolExecutor@486cc147", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPerTaskExecutor@45a37b8c", "parent": "<root>", "owner": null, "threads": [ { "tid": "32", "name": "handler-5", "stack": [ "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:621)", "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:793)", "java.base\/java.lang.Thread.sleep(Thread.java:556)", "java.base\/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)", "org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:109)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:871)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:847)", "java.base\/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314)", "java.base\/java.lang.VirtualThread.run(VirtualThread.java:309)" ] } ], "threadCount": "1" }, { "container": "ForkJoinPool-1\/jdk.internal.vm.SharedThreadContainer@a5da302", "parent": "<root>", "owner": null, "threads": [ { "tid": "33", "name": "ForkJoinPool-1-worker-3", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkUntil(LockSupport.java:449)", "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1891)", "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1809)", "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)" ] } ], "threadCount": "1" }, { "container": "java.util.concurrent.ScheduledThreadPoolExecutor@4d8bd5e9", "parent": "<root>", "owner": null, "threads": [ { "tid": "26", "name": "VirtualThread-unparker", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1758)", "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)", "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)", "java.base\/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1070)", "java.base\/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)", "java.base\/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] } ], "threadCount": "1" } ] } }
Virtual Threadsã¯ãã®éšåã§ããã
{ "container": "java.util.concurrent.ThreadPerTaskExecutor@45a37b8c", "parent": "<root>", "owner": null, "threads": [ { "tid": "32", "name": "handler-5", "stack": [ "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:621)", "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:793)", "java.base\/java.lang.Thread.sleep(Thread.java:556)", "java.base\/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)", "org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:109)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:871)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:847)", "java.base\/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314)", "java.base\/java.lang.VirtualThread.run(VirtualThread.java:309)" ] } ], "threadCount": "1" },
JSON圢åŒãªã®ã§ã¡ãã£ãšããã¯ãªããŸãããVirtual Threadsãã©ãã«å±ããŠããããªã©ãããã£ãŠè¯ãã§ããã
-format
ãæå®ããªãå Žåã¯ãplain
ã«ãªããŸãã
$ jcmd $(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1) Thread.dump_to_file thread_dump.txt
ãŸããããã©ã«ãã§ã¯åºåå ã«ãã§ã«ãã¡ã€ã«ãããå Žåã¯äžæžãããŸããã
$ jcmd $(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1) Thread.dump_to_file thread_dump.txt 13711: /path/to/thread_dump.txt exists, use -overwrite to overwrite
ååšãããã¡ã€ã«ãäžæžãããå Žåã¯ã-overwrite
ãªãã·ã§ã³ãæå®ããŸãã
$ jcmd $(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1) Thread.dump_to_file -overwrite thread_dump.txt 13711: Created /path/to/thread_dump.txt
ä»ãããã€ãèŠãŠã¿ãŸãããã
synchronized
ã䜿ã£ãå Žåã
$ curl localhost:8080/synchronized-lock
ã¹ã¬ãããã³ããååŸããŠ
$ jcmd $(jcmd -l | grep SimpleHttpServer | cut -d' ' -f1) Thread.dump_to_file -format=json -overwrite thread_dump.json
確èªã
{ "threadDump": { "processId": "13711", "time": "2023-12-12T14:22:17.829867481Z", "runtimeVersion": "21.0.1+12-Ubuntu-222.04", "threadContainers": [ { "container": "<root>", "parent": null, "owner": null, "threads": [ { "tid": "9", "name": "Reference Handler", "stack": [ "java.base\/java.lang.ref.Reference.waitForReferencePendingList(Native Method)", "java.base\/java.lang.ref.Reference.processPendingReferences(Reference.java:246)", "java.base\/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:208)" ] }, { "tid": "10", "name": "Finalizer", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.lang.Object.wait(Object.java:339)", "java.base\/java.lang.ref.NativeReferenceQueue.await(NativeReferenceQueue.java:48)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:158)", "java.base\/java.lang.ref.NativeReferenceQueue.remove(NativeReferenceQueue.java:89)", "java.base\/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:173)" ] }, { "tid": "11", "name": "Signal Dispatcher", "stack": [ ] }, { "tid": "18", "name": "Notification Thread", "stack": [ ] }, { "tid": "19", "name": "Common-Cleaner", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1847)", "java.base\/java.lang.ref.ReferenceQueue.await(ReferenceQueue.java:71)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:143)", "java.base\/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:218)", "java.base\/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] }, { "tid": "20", "name": "idle-timeout-task", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.util.TimerThread.mainLoop(Timer.java:563)", "java.base\/java.util.TimerThread.run(Timer.java:516)" ] }, { "tid": "21", "name": "HTTP-Dispatcher", "stack": [ "java.base\/sun.nio.ch.EPoll.wait(Native Method)", "java.base\/sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:121)", "java.base\/sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:130)", "java.base\/sun.nio.ch.SelectorImpl.select(SelectorImpl.java:142)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Dispatcher.run(ServerImpl.java:474)", "java.base\/java.lang.Thread.run(Thread.java:1583)" ] }, { "tid": "23", "name": "DestroyJavaVM", "stack": [ ] }, { "tid": "27", "name": "Attach Listener", "stack": [ "java.base\/java.lang.Thread.getStackTrace(Thread.java:2450)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadToJson(ThreadDumper.java:264)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:237)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:201)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToFile(ThreadDumper.java:115)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:84)" ] } ], "threadCount": "9" }, { "container": "ForkJoinPool.commonPool\/jdk.internal.vm.SharedThreadContainer@7350b626", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPoolExecutor@486cc147", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPerTaskExecutor@45a37b8c", "parent": "<root>", "owner": null, "threads": [ { "tid": "41", "name": "handler-11", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.lang.VirtualThread.parkOnCarrierThread(VirtualThread.java:665)", "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:636)", "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:793)", "java.base\/java.lang.Thread.sleep(Thread.java:556)", "java.base\/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)", "org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:159)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:871)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:847)", "java.base\/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314)", "java.base\/java.lang.VirtualThread.run(VirtualThread.java:309)" ] } ], "threadCount": "1" }, { "container": "ForkJoinPool-1\/jdk.internal.vm.SharedThreadContainer@a5da302", "parent": "<root>", "owner": null, "threads": [ { "tid": "42", "name": "ForkJoinPool-1-worker-6", "stack": [ "java.base\/jdk.internal.vm.Continuation.run(Continuation.java:248)", "java.base\/java.lang.VirtualThread.runContinuation(VirtualThread.java:221)", "java.base\/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1423)", "java.base\/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)", "java.base\/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)", "java.base\/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)", "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)", "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)" ] } ], "threadCount": "1" }, { "container": "java.util.concurrent.ScheduledThreadPoolExecutor@4d8bd5e9", "parent": "<root>", "owner": null, "threads": [ { "tid": "26", "name": "VirtualThread-unparker", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(AbstractQueuedSynchronizer.java:519)", "java.base\/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3780)", "java.base\/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3725)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1707)", "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1170)", "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)", "java.base\/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1070)", "java.base\/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)", "java.base\/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] } ], "threadCount": "1" } ] } }
以éã¯ã¹ã¬ãããã³ããååŸããç®æã¯çç¥ããŸãã
ReentrantLock
ã
$ curl localhost:8080/lock
çµæã
{ "threadDump": { "processId": "13711", "time": "2023-12-12T14:23:14.386811163Z", "runtimeVersion": "21.0.1+12-Ubuntu-222.04", "threadContainers": [ { "container": "<root>", "parent": null, "owner": null, "threads": [ { "tid": "9", "name": "Reference Handler", "stack": [ "java.base\/java.lang.ref.Reference.waitForReferencePendingList(Native Method)", "java.base\/java.lang.ref.Reference.processPendingReferences(Reference.java:246)", "java.base\/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:208)" ] }, { "tid": "10", "name": "Finalizer", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.lang.Object.wait(Object.java:339)", "java.base\/java.lang.ref.NativeReferenceQueue.await(NativeReferenceQueue.java:48)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:158)", "java.base\/java.lang.ref.NativeReferenceQueue.remove(NativeReferenceQueue.java:89)", "java.base\/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:173)" ] }, { "tid": "11", "name": "Signal Dispatcher", "stack": [ ] }, { "tid": "18", "name": "Notification Thread", "stack": [ ] }, { "tid": "19", "name": "Common-Cleaner", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1847)", "java.base\/java.lang.ref.ReferenceQueue.await(ReferenceQueue.java:71)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:143)", "java.base\/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:218)", "java.base\/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] }, { "tid": "20", "name": "idle-timeout-task", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.util.TimerThread.mainLoop(Timer.java:563)", "java.base\/java.util.TimerThread.run(Timer.java:516)" ] }, { "tid": "21", "name": "HTTP-Dispatcher", "stack": [ "java.base\/sun.nio.ch.EPoll.wait(Native Method)", "java.base\/sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:121)", "java.base\/sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:130)", "java.base\/sun.nio.ch.SelectorImpl.select(SelectorImpl.java:142)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Dispatcher.run(ServerImpl.java:474)", "java.base\/java.lang.Thread.run(Thread.java:1583)" ] }, { "tid": "23", "name": "DestroyJavaVM", "stack": [ ] }, { "tid": "27", "name": "Attach Listener", "stack": [ "java.base\/java.lang.Thread.getStackTrace(Thread.java:2450)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadToJson(ThreadDumper.java:264)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:237)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:201)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToFile(ThreadDumper.java:115)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:84)" ] } ], "threadCount": "9" }, { "container": "ForkJoinPool.commonPool\/jdk.internal.vm.SharedThreadContainer@7350b626", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPoolExecutor@486cc147", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPerTaskExecutor@45a37b8c", "parent": "<root>", "owner": null, "threads": [ { "tid": "44", "name": "handler-13", "stack": [ "java.base\/java.lang.VirtualThread.parkNanos(VirtualThread.java:621)", "java.base\/java.lang.VirtualThread.sleepNanos(VirtualThread.java:793)", "java.base\/java.lang.Thread.sleep(Thread.java:556)", "java.base\/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)", "org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:136)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:871)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:847)", "java.base\/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314)", "java.base\/java.lang.VirtualThread.run(VirtualThread.java:309)" ] } ], "threadCount": "1" }, { "container": "ForkJoinPool-1\/jdk.internal.vm.SharedThreadContainer@a5da302", "parent": "<root>", "owner": null, "threads": [ { "tid": "45", "name": "ForkJoinPool-1-worker-7", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkUntil(LockSupport.java:449)", "java.base\/java.util.concurrent.ForkJoinPool.awaitWork(ForkJoinPool.java:1891)", "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1809)", "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)" ] } ], "threadCount": "1" }, { "container": "java.util.concurrent.ScheduledThreadPoolExecutor@4d8bd5e9", "parent": "<root>", "owner": null, "threads": [ { "tid": "26", "name": "VirtualThread-unparker", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1758)", "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)", "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)", "java.base\/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1070)", "java.base\/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)", "java.base\/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] } ], "threadCount": "1" } ] } }
ããŸãé¢çœãçµæã§ã¯ãªããããããŸãããâŠã
CPUãæ¶è²»ããåŠçã
$ curl localhost:8080/heavy
ã¹ã¬ãããã³ãã
{ "threadDump": { "processId": "13711", "time": "2023-12-12T14:23:59.202042310Z", "runtimeVersion": "21.0.1+12-Ubuntu-222.04", "threadContainers": [ { "container": "<root>", "parent": null, "owner": null, "threads": [ { "tid": "9", "name": "Reference Handler", "stack": [ "java.base\/java.lang.ref.Reference.waitForReferencePendingList(Native Method)", "java.base\/java.lang.ref.Reference.processPendingReferences(Reference.java:246)", "java.base\/java.lang.ref.Reference$ReferenceHandler.run(Reference.java:208)" ] }, { "tid": "10", "name": "Finalizer", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.lang.Object.wait(Object.java:339)", "java.base\/java.lang.ref.NativeReferenceQueue.await(NativeReferenceQueue.java:48)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:158)", "java.base\/java.lang.ref.NativeReferenceQueue.remove(NativeReferenceQueue.java:89)", "java.base\/java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:173)" ] }, { "tid": "11", "name": "Signal Dispatcher", "stack": [ ] }, { "tid": "18", "name": "Notification Thread", "stack": [ ] }, { "tid": "19", "name": "Common-Cleaner", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1847)", "java.base\/java.lang.ref.ReferenceQueue.await(ReferenceQueue.java:71)", "java.base\/java.lang.ref.ReferenceQueue.remove0(ReferenceQueue.java:143)", "java.base\/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:218)", "java.base\/jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:140)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] }, { "tid": "20", "name": "idle-timeout-task", "stack": [ "java.base\/java.lang.Object.wait0(Native Method)", "java.base\/java.lang.Object.wait(Object.java:366)", "java.base\/java.util.TimerThread.mainLoop(Timer.java:563)", "java.base\/java.util.TimerThread.run(Timer.java:516)" ] }, { "tid": "21", "name": "HTTP-Dispatcher", "stack": [ "java.base\/sun.nio.ch.EPoll.wait(Native Method)", "java.base\/sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:121)", "java.base\/sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:130)", "java.base\/sun.nio.ch.SelectorImpl.select(SelectorImpl.java:142)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Dispatcher.run(ServerImpl.java:474)", "java.base\/java.lang.Thread.run(Thread.java:1583)" ] }, { "tid": "23", "name": "DestroyJavaVM", "stack": [ ] }, { "tid": "27", "name": "Attach Listener", "stack": [ "java.base\/java.lang.Thread.getStackTrace(Thread.java:2450)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadToJson(ThreadDumper.java:264)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:237)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:201)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToFile(ThreadDumper.java:115)", "java.base\/jdk.internal.vm.ThreadDumper.dumpThreadsToJson(ThreadDumper.java:84)" ] } ], "threadCount": "9" }, { "container": "ForkJoinPool.commonPool\/jdk.internal.vm.SharedThreadContainer@7350b626", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPoolExecutor@486cc147", "parent": "<root>", "owner": null, "threads": [ ], "threadCount": "0" }, { "container": "java.util.concurrent.ThreadPerTaskExecutor@45a37b8c", "parent": "<root>", "owner": null, "threads": [ { "tid": "47", "name": "handler-15", "stack": [ "java.base\/java.time.Duration.toMillis(Duration.java:1240)", "org.littlewings.virtualthreads.SimpleHttpServer.lambda$createHandler$1(SimpleHttpServer.java:125)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:101)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:871)", "jdk.httpserver\/com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:98)", "jdk.httpserver\/sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:847)", "java.base\/java.util.concurrent.ThreadPerTaskExecutor$TaskRunner.run(ThreadPerTaskExecutor.java:314)", "java.base\/java.lang.VirtualThread.run(VirtualThread.java:309)" ] } ], "threadCount": "1" }, { "container": "ForkJoinPool-1\/jdk.internal.vm.SharedThreadContainer@a5da302", "parent": "<root>", "owner": null, "threads": [ { "tid": "48", "name": "ForkJoinPool-1-worker-8", "stack": [ "java.base\/jdk.internal.vm.Continuation.run(Continuation.java:248)", "java.base\/java.lang.VirtualThread.runContinuation(VirtualThread.java:221)", "java.base\/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1423)", "java.base\/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)", "java.base\/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)", "java.base\/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)", "java.base\/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)", "java.base\/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)" ] } ], "threadCount": "1" }, { "container": "java.util.concurrent.ScheduledThreadPoolExecutor@4d8bd5e9", "parent": "<root>", "owner": null, "threads": [ { "tid": "26", "name": "VirtualThread-unparker", "stack": [ "java.base\/jdk.internal.misc.Unsafe.park(Native Method)", "java.base\/java.util.concurrent.locks.LockSupport.park(LockSupport.java:371)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(AbstractQueuedSynchronizer.java:519)", "java.base\/java.util.concurrent.ForkJoinPool.unmanagedBlock(ForkJoinPool.java:3780)", "java.base\/java.util.concurrent.ForkJoinPool.managedBlock(ForkJoinPool.java:3725)", "java.base\/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1707)", "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1170)", "java.base\/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)", "java.base\/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1070)", "java.base\/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)", "java.base\/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)", "java.base\/java.lang.Thread.run(Thread.java:1583)", "java.base\/jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:186)" ] } ], "threadCount": "1" } ] } }
ã¹ã¬ãããã³ãã¯ãããããã«ããŠãããŸãããã
HttpClientã§ãã¹ãããŠã¿ã
ãããŸã§ã®å
容ããHttpClient
ã䜿ã£ãŠãã¹ãããŠã¿ãŸãã
HttpClient (Java SE 21 & JDK 21)
äœæãããã¹ãã¯ãã¡ãã
src/test/java/org/littlewings/virtualthreads/SimpleHttpServerTest.java
package org.littlewings.virtualthreads; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; class SimpleHttpServerTest { static { System.setProperty("jdk.virtualThreadScheduler.parallelism", "1"); System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", "1"); } SimpleHttpServer simpleHttpServer; @BeforeEach void setUp() { simpleHttpServer = SimpleHttpServer.create(18080); simpleHttpServer.start(); } @AfterEach void tearDown() { simpleHttpServer.stop(); } @Test void simple() throws InterruptedException, ExecutionException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = 0; long elapsedTime = 0; // simple CompletableFuture<HttpResponse<String>> simpleResponse = httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ); assertThat(simpleResponse.get().body()).isEqualTo("Hello World."); // sleep startTime = System.currentTimeMillis(); CompletableFuture<HttpResponse<String>> sleepResponse = httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/sleep")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ); assertThat(sleepResponse.get().body()).isEqualTo("sleep."); elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); // cpu heavy startTime = System.currentTimeMillis(); CompletableFuture<HttpResponse<String>> heavyResponse = httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/heavy")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ); assertThat(heavyResponse.get().body()).isEqualTo("heavy."); elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); // lock startTime = System.currentTimeMillis(); CompletableFuture<HttpResponse<String>> lockResponse = httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/lock")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ); assertThat(lockResponse.get().body()).isEqualTo("lock."); elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); // synchronized lock startTime = System.currentTimeMillis(); CompletableFuture<HttpResponse<String>> synchronizedLockResponse = httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/synchronized-lock")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ); assertThat(synchronizedLockResponse.get().body()).isEqualTo("synchronized lock."); elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); } } @Test void concurrentCallSleepEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = IntStream .rangeClosed(1, 3) .mapToObj(i -> httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/sleep")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ) .toList(); for (CompletableFuture<HttpResponse<String>> response : responses) { assertThat(response.get().body()).isEqualTo("sleep."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); } } @Test void concurrentCallCpuHeavyEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = IntStream .rangeClosed(1, 3) .mapToObj(i -> httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/heavy")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ) .toList(); for (CompletableFuture<HttpResponse<String>> response : responses) { assertThat(response.get().body()).isEqualTo("heavy."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(9000L); assertThat(elapsedTime).isLessThan(10000L); } } @Test void concurrentCallLockEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = IntStream .rangeClosed(1, 3) .mapToObj(i -> httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/lock")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ) .toList(); for (CompletableFuture<HttpResponse<String>> response : responses) { assertThat(response.get().body()).isEqualTo("lock."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(9000L); assertThat(elapsedTime).isLessThan(10000L); } } @Test void concurrentCallSynchronizedLockEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = IntStream .rangeClosed(1, 3) .mapToObj(i -> httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/synchronized-lock")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ) .toList(); for (CompletableFuture<HttpResponse<String>> response : responses) { assertThat(response.get().body()).isEqualTo("synchronized lock."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(9000L); assertThat(elapsedTime).isLessThan(10000L); } } @Test void concurrentCallLockMixedEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = List.of( httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/lock")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ), httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/lock2")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ); assertThat(responses.get(0).get().body()).isEqualTo("lock."); assertThat(responses.get(1).get().body()).isEqualTo("lock2."); long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); } } @Test void concurrentCallSynchronizedLockMixedEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = List.of( httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/synchronized-lock")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ), httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/synchronized-lock2")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ); assertThat(responses.get(0).get().body()).isEqualTo("synchronized lock."); assertThat(responses.get(1).get().body()).isEqualTo("synchronized lock2."); long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(6000L); assertThat(elapsedTime).isLessThan(7000L); } } }
ãã¹ãéå§æã«HTTPãµãŒããŒãèµ·åãããã¹ãçµäºæã«åæ¢ããŸãããŸãVirtual Threadsã®äžŠå床ãšãã©ãããã©ãŒã ã¹ã¬ããæ°ã¯1ã«
ããŠãããŸãã
static { System.setProperty("jdk.virtualThreadScheduler.parallelism", "1"); System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", "1"); } SimpleHttpServer simpleHttpServer; @BeforeEach void setUp() { simpleHttpServer = SimpleHttpServer.create(18080); simpleHttpServer.start(); } @AfterEach void tearDown() { simpleHttpServer.stop(); }
ããšã¯åºæ¬çã«curlã§ç¢ºèªããããšãšé¡äŒŒã®ããšãããŠããŸãã
ã¹ãªãŒããããšã³ããã€ã³ãã«3å䞊è¡ã«ã¢ã¯ã»ã¹ããŠãã¬ã¹ãã³ã¹ãåãåããŸã§3ç§ã»ã©ãªããšã確èªã
@Test void concurrentCallSleepEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = IntStream .rangeClosed(1, 3) .mapToObj(i -> httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/sleep")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ) .toList(); for (CompletableFuture<HttpResponse<String>> response : responses) { assertThat(response.get().body()).isEqualTo("sleep."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); } }
ç°ãªãããã¯ãååŸããReentrantLock
ã䜿ãå Žåãšãç°ãªãã€ã³ã¹ã¿ã³ã¹ã«å¯ŸããŠsynchronized
ã§ããã¯ãååŸãããšå®è¡æéã«
å·®ãåºãããšããªã©ã§ããã
@Test void concurrentCallLockMixedEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = List.of( httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/lock")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ), httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/lock2")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ); assertThat(responses.get(0).get().body()).isEqualTo("lock."); assertThat(responses.get(1).get().body()).isEqualTo("lock2."); long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); } } @Test void concurrentCallSynchronizedLockMixedEndpoint() throws ExecutionException, InterruptedException { try (HttpClient httpClient = HttpClient.newBuilder().build()) { long startTime = System.currentTimeMillis(); List<CompletableFuture<HttpResponse<String>>> responses = List.of( httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/synchronized-lock")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ), httpClient.sendAsync( HttpRequest.newBuilder().uri(URI.create("http://localhost:18080/synchronized-lock2")).GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) ) ); assertThat(responses.get(0).get().body()).isEqualTo("synchronized lock."); assertThat(responses.get(1).get().body()).isEqualTo("synchronized lock2."); long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(6000L); assertThat(elapsedTime).isLessThan(7000L); } }
ããããã³ã°ãªAPIïŒSocketïŒã䜿ã£ãHTTPã¯ã©ã€ã¢ã³ããšVirtual Threadsã®çµã¿åãããè©Šã
HttpClient
ã¯å
éšã§ãã³ããããã³ã°IOã䜿ã£ãŠããããã§ããããšãã°ããã¡ãã
ãªã®ã§ãVirtual Threadsã䜿ãã®ã§ããã°ãããããã³ã°ãªAPIã䜿ã£ãŠãããã¯ããæã«ä»®æ³ã¹ã¬ãããåãæ¿ãã£ãŠåäœãããšããã
確èªããããã®ã§ãã
ãšããããã§ãæåŸã«java.net.Socket
ã䜿ã£ãŠç°¡åãªHTTPã¯ã©ã€ã¢ã³ããäœæããŠãVirtual Threadsãšçµã¿åãããŠè©ŠããŠã¿ãŸãã
java.net.Socket
ãæžãçŽãããŠããŠãå
éšçã«ã¯Virtual Threadsã䜿ããšãã³ããããã³ã°ã«ãªãããã§ãã
APIã®äœ¿ãæ¹ã¯å€ãããªãã¯ããªã®ã§ã確èªãšããããšã§ã
äœæãããã¹ãã¯ãã¡ãã
src/test/java/org/littlewings/virtualthreads/BlockingSocketClientTest.java
package org.littlewings.virtualthreads; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.*; import java.net.Socket; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.*; import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; class BlockingSocketClientTest { static { // ã¯ã©ã€ã¢ã³ãåã System.setProperty("jdk.virtualThreadScheduler.parallelism", "1"); System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", "1"); } ForkedSimpleHttpServer forkedSimpleHttpServer; @BeforeEach void setUp() { forkedSimpleHttpServer = ForkedSimpleHttpServer.start(28080); try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { // ignore } } @AfterEach void tearDown() { forkedSimpleHttpServer.stop(); } // ããã«ããã¹ããæžã static class SimpleHttpClient { String get(URI uri) { try (Socket socket = new Socket(uri.getHost(), uri.getPort()); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) { writer.write(String.format("GET %s HTTP/1.1", uri.getPath())); writer.write("\r\n"); writer.write(String.format("Host: %s", uri.getHost())); writer.write("\r\n"); writer.write("Connection: close"); writer.write("\r\n"); writer.write("\r\n"); writer.flush(); String line; // skip headers while ((line = reader.readLine()) != null) { if (line.isEmpty()) { break; } } return reader.readLine(); } catch (IOException e) { throw new UncheckedIOException(e); } } } static class ForkedSimpleHttpServer { private Process process; ForkedSimpleHttpServer(Process process) { this.process = process; } static ForkedSimpleHttpServer start(int port) { try { Process process = new ProcessBuilder().command(List.of( "java", // ãµãŒããŒåŽã¯äžŠå床ãšã¹ã¬ããæ°ãå¢ãã "-Djdk.virtualThreadScheduler.parallelism=4", "-Djdk.virtualThreadScheduler.maxPoolSize=4", "-cp", "target/classes", "org.littlewings.virtualthreads.SimpleHttpServer", Integer.toString(port) )).start(); Thread.ofPlatform().daemon(true).start(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { reader.lines().forEach(System.out::println); } catch (Exception e) { // ignore } }); return new ForkedSimpleHttpServer(process); } catch (IOException e) { throw new UncheckedIOException(e); } } void stop() { process.destroy(); } } }
Socket
ã䜿ã£ãèªäœã®HTTPã¯ã©ã€ã¢ã³ãã¯ãã¡ãã§ãããšããããGETã¡ãœãããåŒã¹ãã ãã§ããã以å€ã¯ãªã«ãã§ããŸããã
static class SimpleHttpClient { String get(URI uri) { try (Socket socket = new Socket(uri.getHost(), uri.getPort()); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) { writer.write(String.format("GET %s HTTP/1.1", uri.getPath())); writer.write("\r\n"); writer.write(String.format("Host: %s", uri.getHost())); writer.write("\r\n"); writer.write("Connection: close"); writer.write("\r\n"); writer.write("\r\n"); writer.flush(); String line; // skip headers while ((line = reader.readLine()) != null) { if (line.isEmpty()) { break; } } return reader.readLine(); } catch (IOException e) { throw new UncheckedIOException(e); } } }
Virtual Threadsã®äžŠå床ãšãã©ãããã©ãŒã ã¹ã¬ããæ°ã¯1ã«ããã®ã§ãããããã¯ã¯ã©ã€ã¢ã³ãåãã«ããŸãã1ã§ãã£ãŠãã
ããããã³ã°æäœã§ããã°ä»®æ³ã¹ã¬ãããã¢ã³ããŠã³ããããŠå¥ã®ä»®æ³ã¹ã¬ããã§åãã¯ãã§ããããã
static { // ã¯ã©ã€ã¢ã³ãåã System.setProperty("jdk.virtualThreadScheduler.parallelism", "1"); System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", "1"); }
ãããŠããã®ã·ã¹ãã ããããã£ã®åœ±é¿ãHTTPãµãŒããŒåŽãåããªãããã«HTTPãµãŒããŒã¯å¥ããã»ã¹ã§èµ·åããããã«ããŸããã
䞊å床ããã³ãã©ãããã©ãŒã ã¹ã¬ããæ°ãå€ãå²ãåœãŠãŠããŸãã
ForkedSimpleHttpServer forkedSimpleHttpServer; @BeforeEach void setUp() { forkedSimpleHttpServer = ForkedSimpleHttpServer.start(28080); try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { // ignore } } @AfterEach void tearDown() { forkedSimpleHttpServer.stop(); } static class ForkedSimpleHttpServer { private Process process; ForkedSimpleHttpServer(Process process) { this.process = process; } static ForkedSimpleHttpServer start(int port) { try { Process process = new ProcessBuilder().command(List.of( "java", // ãµãŒããŒåŽã¯äžŠå床ãšã¹ã¬ããæ°ãå¢ãã "-Djdk.virtualThreadScheduler.parallelism=4", "-Djdk.virtualThreadScheduler.maxPoolSize=4", "-cp", "target/classes", "org.littlewings.virtualthreads.SimpleHttpServer", Integer.toString(port) )).start(); Thread.ofPlatform().daemon(true).start(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { reader.lines().forEach(System.out::println); } catch (Exception e) { // ignore } }); return new ForkedSimpleHttpServer(process); } catch (IOException e) { throw new UncheckedIOException(e); } } void stop() { process.destroy(); } }
ç°¡åã«ãã¹ãã
@Test void simple() { SimpleHttpClient simpleHttpClient = new SimpleHttpClient(); String simple = simpleHttpClient.get(URI.create("http://localhost:28080/")); assertThat(simple).isEqualTo("Hello World."); String sleep = simpleHttpClient.get(URI.create("http://localhost:28080/sleep")); assertThat(sleep).isEqualTo("sleep."); String heavy = simpleHttpClient.get(URI.create("http://localhost:28080/heavy")); assertThat(heavy).isEqualTo("heavy."); String lock = simpleHttpClient.get(URI.create("http://localhost:28080/lock")); assertThat(lock).isEqualTo("lock."); String synchronizedLock = simpleHttpClient.get(URI.create("http://localhost:28080/synchronized-lock")); assertThat(synchronizedLock).isEqualTo("synchronized lock."); }
Virtual ThreadsïŒExecutors#newVirtualThreadPerTaskExecutor
ïŒã䜿ã£ã䞊è¡ã¢ã¯ã»ã¹ãã¹ãªãŒããããŠã¿ãŸãã
@Test void concurrentCallSleepEndpoint() throws ExecutionException, InterruptedException { ExecutorService es = Executors.newVirtualThreadPerTaskExecutor(); SimpleHttpClient simpleHttpClient = new SimpleHttpClient(); long startTime = System.currentTimeMillis(); List<Future<String>> futures = IntStream .rangeClosed(1, 3) .mapToObj(i -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/sleep")))) .toList(); for (Future<String> future : futures) { assertThat(future.get()).isEqualTo("sleep."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); }
次ã«ãCPUãæ¶è²»ãã/heavy
ãããã¯ãHTTPãµãŒããŒåŽã®äžŠå床ãšãã©ãããã©ãŒã ã¹ã¬ããæ°ãäžããŠããã®ã§ããã®å
ãªã¯ãšã¹ããåæã«åŠçã§ããããã«ãªã£ãŠããŸãã
@Test void concurrentCallCpuHeavyEndpoint() throws ExecutionException, InterruptedException { ExecutorService es = Executors.newVirtualThreadPerTaskExecutor(); SimpleHttpClient simpleHttpClient = new SimpleHttpClient(); long startTime = System.currentTimeMillis(); List<Future<String>> futures = IntStream .rangeClosed(1, 3) .mapToObj(i -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/heavy")))) .toList(); for (Future<String> future : futures) { assertThat(future.get()).isEqualTo("heavy."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); }
åãã€ã³ã¹ã¿ã³ã¹ã«å¯Ÿããããã¯ãååŸãããã®ã«ã€ããŠã¯ãåœç¶ãªããã¢ã¯ã»ã¹ãçŽåã«ãªããŸãã
@Test void concurrentCallLockEndpoint() throws ExecutionException, InterruptedException { ExecutorService es = Executors.newVirtualThreadPerTaskExecutor(); SimpleHttpClient simpleHttpClient = new SimpleHttpClient(); long startTime = System.currentTimeMillis(); List<Future<String>> futures = IntStream .rangeClosed(1, 3) .mapToObj(i -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/lock")))) .toList(); for (Future<String> future : futures) { assertThat(future.get()).isEqualTo("lock."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(9000L); assertThat(elapsedTime).isLessThan(10000L); } @Test void concurrentCallSynchronizedLockEndpoint() throws ExecutionException, InterruptedException { ExecutorService es = Executors.newVirtualThreadPerTaskExecutor(); SimpleHttpClient simpleHttpClient = new SimpleHttpClient(); long startTime = System.currentTimeMillis(); List<Future<String>> futures = IntStream .rangeClosed(1, 3) .mapToObj(i -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/synchronized-lock")))) .toList(); for (Future<String> future : futures) { assertThat(future.get()).isEqualTo("synchronized lock."); } long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(9000L); assertThat(elapsedTime).isLessThan(10000L); }
ããã¯å¯Ÿè±¡ã®ã€ã³ã¹ã¿ã³ã¹ãå€ããã°ãHTTPãµãŒããŒåŽã¯ã¹ã¬ããæ°åã ãã¯äžŠåã«åããããã«ãªãã®ã§ãã¯ã©ã€ã¢ã³ãåŽã®
䞊å床ããã³ãã©ãããã©ãŒã ã¹ã¬ããæ°ã1ã§ãããŸãåãæ¿ããããããã«ãªã£ãŠããŸãã
ãã£ãŠãsynchoronized
ãããã¯ã䜿ãåŠçã«ã¢ã¯ã»ã¹ããŠã䞊åã«åäœããããã«ãªããŸããããã£ãšããCPUæ°åãäžéã§ããã
@Test void concurrentCallLockMixedEndpoint() throws ExecutionException, InterruptedException { ExecutorService es = Executors.newVirtualThreadPerTaskExecutor(); SimpleHttpClient simpleHttpClient = new SimpleHttpClient(); long startTime = System.currentTimeMillis(); List<Future<String>> futures = IntStream .rangeClosed(1, 2) .mapToObj(i -> switch (i) { case 1 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/lock"))); case 2 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/lock2"))); default -> throw new IllegalArgumentException(); }) .toList(); assertThat(futures.get(0).get()).isEqualTo("lock."); assertThat(futures.get(1).get()).isEqualTo("lock2."); long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); } @Test void concurrentCallSynchronizedLockMixedEndpoint() throws ExecutionException, InterruptedException { ExecutorService es = Executors.newVirtualThreadPerTaskExecutor(); SimpleHttpClient simpleHttpClient = new SimpleHttpClient(); long startTime = System.currentTimeMillis(); List<Future<String>> futures = IntStream .rangeClosed(1, 2) .mapToObj(i -> switch (i) { case 1 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/synchronized-lock"))); case 2 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/synchronized-lock2"))); default -> throw new IllegalArgumentException(); }) .toList(); assertThat(futures.get(0).get()).isEqualTo("synchronized lock."); assertThat(futures.get(1).get()).isEqualTo("synchronized lock2."); long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); } @Test void concurrentCallAllMixedEndpoint() throws ExecutionException, InterruptedException { ExecutorService es = Executors.newVirtualThreadPerTaskExecutor(); SimpleHttpClient simpleHttpClient = new SimpleHttpClient(); long startTime = System.currentTimeMillis(); List<Future<String>> futures = IntStream .rangeClosed(1, 6) .mapToObj(i -> switch (i) { case 1 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/sleep"))); case 2 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/heavy"))); case 3 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/lock"))); case 4 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/lock2"))); case 5 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/synchronized-lock"))); case 6 -> es.submit(() -> simpleHttpClient.get(URI.create("http://localhost:28080/synchronized-lock2"))); default -> throw new IllegalArgumentException(); }) .toList(); assertThat(futures.get(0).get()).isEqualTo("sleep."); assertThat(futures.get(1).get()).isEqualTo("heavy."); assertThat(futures.get(2).get()).isEqualTo("lock."); assertThat(futures.get(3).get()).isEqualTo("lock2."); assertThat(futures.get(4).get()).isEqualTo("synchronized lock."); assertThat(futures.get(5).get()).isEqualTo("synchronized lock2."); long elapsedTime = System.currentTimeMillis() - startTime; assertThat(elapsedTime).isGreaterThan(3000L); assertThat(elapsedTime).isLessThan(4000L); }
OKããã§ããã
ããã§ããããããšã¯ã²ãšãšãã確èªã§ããŸããã
ãããã«
Virtual Threadsã®ç¢ºèªãšããããšã§ãHTTPãµãŒããŒããã³ã¯ã©ã€ã¢ã³ããæžããŠè©ŠããŠã¿ãŸããã
ãããã¯ããåŠçã§ä»®æ³ã¹ã¬ãããåãæ¿ããããšããsynchronized
ã䜿ããšã¢ã³ããŠã³ãã§ããªããªãããšããããŠæ°ãã
ã¹ã¬ãããã³ãã®åœ¢åŒã確èªã§ããŸããã®ã§Virtual Threadsã«å¯Ÿããç解ãé²ãã ããªãšæããŸãã
ããããVirtual Threadsã䜿ã£ãŠãããã©ããã¯ãŸã ããããŸããããåºç€çãªå 容ãšããŠæŒãããŠããããšæããŸãã