1. Algorithm 每周一道算法题
本周算法题是寻找旋转数组最小值
本题的思想是通过判断左右有序性进行分类解答
2. Review 阅读一篇英文文章
本周阅读的文章是 Virtual Threads
Creating and Running a Virtual Thread
The Thread and Thread.Builder APIs provide ways to create both platform and virtual threads. The java.util.concurrent.Executors class also defines methods to create an ExecutorService that starts a new virtual thread for each task.
Thread 和 Thread.Builder API 提供了创建平台级线程和虚拟线程的方法。java.util.concurrent.Executors 类同样定义了方法创建每个任务对应一个虚拟线程的 ExecutorService。
Topics
- Creating a Virtual Thread with the Thread Class and the Thread.Builder Interface
- Creating and Running a Virtual Thread with the Executors.newVirtualThreadPerTaskExecutor() Method
- Multithreaded Client Server Example
Creating a Virtual Thread with the Thread Class and the Thread.Builder Interface
Call the Thread.ofVirtual() method to create an instance of Thread.Builder for creating virtual threads.
通过调用 Thread.ofVirtual() 方法创建一个 Thread.Builder 实例以创建虚拟线程
The following example creates and starts a virtual thread that prints a message. It calls the join method to wait for the virtual thread to terminate. (This enables you to see the printed message before the main thread terminates.)
以下例子创建并启动一个虚拟线程打印一条消息。它调用了 join 方法等待虚拟线程执行结束。(这使得你可以在主线程结束前看到打印的消息)
Copy
Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));
thread.join();
The Thread.Builder interface lets you create threads with common Thread properties such as the thread's name. The Thread.Builder.OfPlatform subinterface creates platform threads while Thread.Builder.OfVirtual creates virtual threads.
Thread.Builder 接口让你使用通用的线程参数比如 name 创建线程。Thread.Builder.OfPlatform 子接口创建平台级线程,Thread.Builder.OfVirtual 创建虚拟线程。
The following example creates a virtual thread named MyThread with the Thread.Builder interface:
以下创建虚拟线程的例子叫做 MyThread,就使用了 Thread.Builder 接口
Copy
Thread.Builder builder = Thread.ofVirtual().name("MyThread");
Runnable task = () -> {
System.out.println("Running thread");
};
Thread t = builder.start(task);
System.out.println("Thread t name: " + t.getName());
t.join();
The following example creates and starts two virtual threads with Thread.Builder:
下面使用 Thread.Builder 接口创建并启动了两个虚拟线程:
Copy
Thread.Builder builder = Thread.ofVirtual().name("worker-", 0);
Runnable task = () -> {
System.out.println("Thread ID: " + Thread.currentThread().threadId());
};
// name "worker-0"
Thread t1 = builder.start(task);
t1.join();
System.out.println(t1.getName() + " terminated");
// name "worker-1"
Thread t2 = builder.start(task);
t2.join();
System.out.println(t2.getName() + " terminated");
This example prints output similar to the following:
这个例子打印结果如下所示:
Copy
Thread ID: 21
worker-0 terminated
Thread ID: 24
worker-1 terminated
Creating and Running a Virtual Thread with the Executors.newVirtualThreadPerTaskExecutor() Method
Executors let you to separate thread management and creation from the rest of your application.
Executors可以让您在应用程序的其他部分之外管理和创建线程。
The following example creates an ExecutorService with the Executors.newVirtualThreadPerTaskExecutor() method. Whenever ExecutorService.submit(Runnable) is called, a new virtual thread is created and started to run the task. This method returns an instance of Future. Note that the method Future.get() waits for the thread's task to complete. Consequently, this example prints a message once the virtual thread's task is complete.
下面例子通过 Executors.newVirtualThreadPerTaskExecutor() 方法创建ExecutorService。任何时候当 ExecutorService.submit(Runnable) 方法被调用时,一个虚拟线程就被创建并且开始执行一个任务。这个方法返回 Future 实例。请注意,Future.get() 方法会等到线程任务执行结束才被调用。因此,这个打印一条消息的例子会等到虚拟线程执行结束才打印。
Copy
try (ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<?> future = myExecutor.submit(() -> System.out.println("Running thread"));
future.get();
System.out.println("Task completed");
// ...
Multithreaded Client Server Example
The following example consists of two classes. EchoServer is a server program that listens on a port and starts a new virtual thread for each connection. EchoClient is a client program that connects to the server and sends messages entered on the command line.
下面例子由两个类组成。EchoServer 是一个监听端口并且每来一个连接就创建一个虚拟线程去执行的服务端程序。EchoClient 是一个连接服务端程序并且发送从命令行输入的消息的客户端程序。
EchoClient creates a socket, thereby getting a connection to EchoServer. It reads input from the user on the standard input stream, and then forwards that text to EchoServer by writing the text to the socket. EchoServer echoes the input back through the socket to the EchoClient. EchoClient reads and displays the data passed back to it from the server. EchoServer can service multiple clients simultaneously through virtual threads, one thread per each client connection.
EchoClient 创建了一个 socket,因此获得了一个和服务端 EchoServer 的连接。它从用户的标准输入读取数据,然后将数据通过写入 socket 发送到 EchoServer 服务端。EchoServer 通过回传接收到的数据给 EchoClient。EchoClient 读取并且打印从服务端返回的数据。EchoServer 能从多个客户端通过虚拟线程接收数据,一个线程对应一个客户端连接
Copy
public class EchoServer {
public static void main(String[] args) throws IOException {
if (args.length != 1) {
System.err.println("Usage: java EchoServer <port>");
System.exit(1);
}
int portNumber = Integer.parseInt(args[0]);
try (
ServerSocket serverSocket =
new ServerSocket(Integer.parseInt(args[0]));
) {
while (true) {
Socket clientSocket = serverSocket.accept();
// Accept incoming connections
// Start a service thread
Thread.ofVirtual().start(() -> {
try (
PrintWriter out =
new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
out.println(inputLine);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
} catch (IOException e) {
System.out.println("Exception caught when trying to listen on port "
+ portNumber + " or listening for a connection");
System.out.println(e.getMessage());
}
}
}
Copy
public class EchoClient {
public static void main(String[] args) throws IOException {
if (args.length != 2) {
System.err.println(
"Usage: java EchoClient <hostname> <port>");
System.exit(1);
}
String hostName = args[0];
int portNumber = Integer.parseInt(args[1]);
try (
Socket echoSocket = new Socket(hostName, portNumber);
PrintWriter out =
new PrintWriter(echoSocket.getOutputStream(), true);
BufferedReader in =
new BufferedReader(
new InputStreamReader(echoSocket.getInputStream()));
) {
BufferedReader stdIn =
new BufferedReader(
new InputStreamReader(System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println("echo: " + in.readLine());
if (userInput.equals("bye")) break;
}
} catch (UnknownHostException e) {
System.err.println("Don't know about host " + hostName);
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't get I/O for the connection to " +
hostName);
System.exit(1);
}
}
}
Scheduling Virtual Threads and Pinned Virtual Threads
The operating system schedules when a platform thread is run. However, the Java runtime schedules when a virtual thread is run. When the Java runtime schedules a virtual thread, it assigns or mounts the virtual thread on a platform thread, then the operating system schedules that platform thread as usual. This platform thread is called a carrier. After running some code, the virtual thread can unmount from its carrier. This usually happens when the virtual thread performs a blocking I/O operation. After a virtual thread unmounts from its carrier, the carrier is free, which means that the Java runtime scheduler can mount a different virtual thread on it.
当平台级线程运行时是操作系统来进行调度的,但是,虚拟线程运行时则是 Java 运行时来进行调度。当 Java 运行时调度一个虚拟线程时,将虚拟线程赋予或者绑定到一个平台级线程,然后操作系统像平常一样调度该平台级线程。合格平台级线程被称作 carrier 线程。这通常发生在虚拟线程执行阻塞的 IO 操作时。当一个虚拟线程从它的 carrier 线程被卸载时,该 carrier 线程就被释放了,这意味着 Java 运行时可以绑定不同的虚拟线程到该 carrier 线程。
A virtual thread cannot be unmounted during blocking operations when it is pinned to its carrier. A virtual thread is pinned in the following situations:
当虚拟线程被固定在其载体上进行阻塞操作时,无法卸载虚拟线程。虚拟线程在以下情况下被固定:
- The virtual thread runs code inside a
synchronizedblock or method(虚拟线程在 sunchronized 块内运行代码) - The virtual thread runs a
nativemethod or a foreign function (see Foreign Function and Memory API)(虚拟线程运行 native 或者外部方法)
Pinning does not make an application incorrect, but it might hinder its scalability. Try avoiding frequent and long-lived pinning by revising synchronized blocks or methods that run frequently and guarding potentially long I/O operations with java.util.concurrent.locks.ReentrantLock.
pining 不会使程序出错,但是它可以影响其可伸缩性。尝试通过修改频繁运行的 synchronized 块或方法,并通过 java.util.concurrent.locks.ReentrantLock 保护潜在的长时间 I/O 操作来避免频繁和长时间的固定。
3. Techniques/Tips 分享一个小技巧
在使用协程时,不要想着协程能提升程序运行速度(单一任务),协程的主要作用是提升程序吞吐量而不是加快程序运行。
4. Share 分享一个观点
虚拟线程,也可以叫做协程,其本质可以理解为一个 task,该 task 可以 run,可以 yeild,这样一个虚拟线程就能实现普通线程才有的功能,但是大小确大大小于平台级线程。
同时协程最重要的其实是对底层阻塞 api 的修改,需要将阻塞 api 修改为非阻塞,这样在协程调用这些阻塞 api 时,才不会将 carrier 线程阻塞