JDK21虚拟线程

目录

虚拟线程

话题

什么是平台线程?

什么是虚拟线程?

为什么要使用虚拟线程?

创建和运行虚拟线程

使用线程类和线程创建虚拟线程。生成器界面

使用Executor.newVirtualThreadPerTaskExecutor()方法创建和运行虚拟线程

调度虚拟线程和固定虚拟线程

调试虚拟线程

JDK虚拟线程的飞行记录器事件


官方文档的翻译版本

官方文档:Virtual Threads

虚拟线程

虚拟线程是轻量级线程,可以减少编写、维护和调试高吞吐量并发应用程序的工作量。

有关虚拟线程的背景信息,请参阅JEP444。

线程是可以调度的最小处理单元。它与其他此类单元同时运行,并且在很大程度上独立于其他此类单元。它是java.lang.Thread的一个实例。有两种线程,平台线程和虚拟线程。

话题

  • 什么是平台线程?
  • 什么是虚拟线程?
  • 为什么要使用虚拟线程?
  • 创建和运行虚拟线程
  • 调度虚拟线程和固定虚拟线程
  • 调试虚拟线程
  • 虚拟线程:采用指南

什么是平台线程?

平台线程被实现为操作系统(OS)线程周围的瘦包装器。平台线程在其底层操作系统线程上运行Java代码,平台线程在平台线程的整个生命周期中捕获其操作系统线程。因此,可用平台线程的数量被限制为OS线程的数量。

平台线程通常具有由操作系统维护的大型线程堆栈和其他资源。它们适用于运行所有类型的任务,但可能是有限的资源。

什么是虚拟线程?

与平台线程一样,虚拟线程也是java.lang.thread的一个实例。然而,虚拟线程并没有绑定到特定的操作系统线程。虚拟线程仍然在操作系统线程上运行代码。但是,当虚拟线程中运行的代码调用阻塞I/O操作时,Java运行时会挂起虚拟线程,直到可以恢复为止。与挂起的虚拟线程相关联的OS线程现在可以自由地执行其他虚拟线程的操作。

虚拟线程的实现方式与虚拟内存类似。为了模拟大量内存,操作系统将大量虚拟地址空间映射到有限的RAM。同样,为了模拟大量线程,Java运行时将大量虚拟线程映射到少量操作系统线程。

与平台线程不同,虚拟线程通常有一个浅调用堆栈,只执行一个HTTP客户端调用或一个JDBC查询。尽管虚拟线程支持线程本地变量和可继承的线程本地变量,但您应该仔细考虑使用它们,因为单个JVM可能支持数百万个虚拟线程。

虚拟线程适用于运行大部分时间被阻塞的任务,这些任务通常等待I/O操作完成。然而,它们并不适用于长时间运行的CPU密集型操作。

为什么要使用虚拟线程?

在高吞吐量并发应用程序中使用虚拟线程,尤其是那些由大量并发任务组成、花费大量时间等待的应用程序。服务器应用程序是高吞吐量应用程序的示例,因为它们通常处理许多执行阻塞I/O操作(如获取资源)的客户端请求。

虚拟线程不是更快的线程;它们运行代码的速度并不比平台线程快。它们的存在是为了提供规模(更高的吞吐量),而不是速度(更低的延迟)。

创建和运行虚拟线程

线程和线程。生成器API提供了创建平台线程和虚拟线程的方法。java.util.concurrent。Executors类还定义了创建ExecutorService的方法,该方法为每个任务启动一个新的虚拟线程。

话题

  • 使用线程类和线程创建虚拟线程。生成器界面
  • 使用Executor.newVirtualThreadPerTaskExecutor()方法创建和运行虚拟线程
  • 多线程客户端服务器示例

使用线程类和线程创建虚拟线程。生成器界面

调用Thread.ofVirtual()方法来创建Thread的实例。用于创建虚拟线程的生成器。

以下示例创建并启动一个打印消息的虚拟线程。它调用联接方法以等待虚拟线程终止。(这使您能够在主线程终止之前看到打印的消息。)

Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));
thread.join();

生成器界面允许您创建具有通用线程属性(如线程名称)的线程。线程。建设者OfPlatform子接口创建平台线程,而Thread。建设者OfVirtual创建虚拟线程。

以下示例使用thread创建一个名为MyThread的虚拟线程。生成器界面:

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();

以下示例使用Thread创建并启动两个 Thread.Builder

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");

此示例打印类似于以下内容的输出:

Thread ID: 21
worker-0 terminated
Thread ID: 24
worker-1 terminated

使用Executor.newVirtualThreadPerTaskExecutor()方法创建和运行虚拟线程

执行器允许您将线程管理和创建与应用程序的其余部分分开。

以下示例使用Executors.newVirtualThreadPerTaskExecutor()方法创建ExecutorService。每当调用ExecutorService.submit(Runnable)时,都会创建并启动一个新的虚拟线程来运行任务。此方法返回Future的一个实例。请注意,方法Future.get()等待线程的任务完成。因此,此示例在虚拟线程的任务完成后打印一条消息。

try (ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<?> future = myExecutor.submit(() -> System.out.println("Running thread"));
    future.get();
    System.out.println("Task completed");
    // ...

多线程客户端服务器示例

以下示例由两个类组成。EchoServer是一个服务器程序,它侦听端口并为每个连接启动一个新的虚拟线程。EchoClient是一个连接到服务器并发送在命令行上输入的消息的客户端程序。

EchoClient创建一个套接字,从而连接到EchoServer。它在标准输入流上读取用户的输入,然后通过将文本写入套接字将文本转发到EchoServer。EchoServer通过插座将输入回波至EchoClient。EchoClient读取并显示从服务器传回的数据。EchoServer可以通过虚拟线程同时为多个客户端提供服务,每个客户端连接一个线程

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());
        }
    }
}
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);
        } 
    }
}

调度虚拟线程和固定虚拟线程

操作系统安排平台线程何时运行。但是,Java运行时会安排虚拟线程何时运行。当Java运行时调度虚拟线程时,它将虚拟线程分配或装载到平台线程上,然后操作系统照常调度该平台线程。这个平台线程被称为载体。在运行一些代码后,虚拟线程可以从其承载器中卸载。这种情况通常发生在虚拟线程执行阻塞I/O操作时。虚拟线程从其载体上卸载后,载体是空闲的,这意味着Java运行时调度程序可以在其上装载不同的虚拟线程。

当虚拟线程固定在其承载器上时,在阻塞操作期间无法卸载它。虚拟线程在以下情况下被固定:

  • 虚拟线程在同步块或方法内运行代码
  • 虚拟线程运行本机方法或外部函数(请参阅外部函数和内存API)

固定不会使应用程序出错,但可能会阻碍其可扩展性。通过修改频繁运行的同步块或方法,并使用java.util.concurrent.locks保护潜在的长I/O操作,尝试避免频繁和长期的固定。重新输入锁定。

调试虚拟线程

虚拟线程是静态线程;调试器可以像平台线程一样逐步完成它们。JDK Flight Recorder和jcmd工具具有其他功能,可以帮助您观察应用程序中的虚拟线程。

话题

  • JDK虚拟线程的飞行记录器事件
  • 查看jcmd线程转储中的虚拟线程

JDK虚拟线程的飞行记录器事件

JDK飞行记录器(JFR)可以发出以下与虚拟线程相关的事件:

  • jdk.VirtualThreadStart和jdk。VirtualThreadEnd指示虚拟线程何时开始和结束。默认情况下,这些事件处于禁用状态。
  • dk.VirtualThreadPinned 表示虚拟线程被固定(其承载线程未被释放)的时间超过阈值持续时间。此事件默认启用,阈值为20ms。
  • jdk.VirtualThreadSubmitFailed表示启动或取消标记虚拟线程失败,可能是由于资源问题。停放虚拟线程会释放底层承载线程来执行其他工作,而取消标记虚拟线程则会调度它继续执行。默认情况下会启用此事件。

使用按请求线程样式的阻塞I/O API编写简单的同步代码

虚拟线程可以显著提高以每请求线程方式编写的服务器的吞吐量,而不是延迟。在这种风格中,服务器将一个线程专门用于处理每个传入请求的整个持续时间。它至少专用一个线程,因为在处理单个请求时,您可能希望使用更多的线程来同时执行一些任务。

阻塞一个平台线程是昂贵的,因为它保留了线程——一种相对稀缺的资源——而它没有做太多有意义的工作。因为虚拟线程可能很多,所以阻塞它们是廉价的,也是受鼓励的。因此,您应该以直接的同步风格编写代码,并使用阻塞I/O API。

例如,以下代码以非阻塞异步风格编写,不会从虚拟线程中获得太多好处。

CompletableFuture.supplyAsync(info::getUrl, pool)
   .thenCompose(url -> getBodyAsync(url, HttpResponse.BodyHandlers.ofString()))
   .thenApply(info::findImage)
   .thenCompose(url -> getBodyAsync(url, HttpResponse.BodyHandlers.ofByteArray()))
   .thenApply(info::setImageData)
   .thenAccept(this::process)
   .exceptionally(t -> { t.printStackTrace(); return null; });

另一方面,以下代码以同步风格编写,并使用简单的阻塞IO,将受益匪浅:

try {
   String page = getBody(info.getUrl(), HttpResponse.BodyHandlers.ofString());
   String imageUrl = info.findImage(page);
   byte[] data = getBody(imageUrl, HttpResponse.BodyHandlers.ofByteArray());   
   info.setImageData(data);
   process(info);
} catch (Exception ex) {
   t.printStackTrace();
}

这样的代码也更容易在调试器中调试、在探查器中配置文件或使用线程转储进行观察。要观察虚拟线程,请使用jcmd命令创建一个线程转储:

jcmd <pid> Thread.dump_to_file -format=json <file>

以这种风格编写的堆栈越多,虚拟线程的性能和可观察性就越好。以其他风格编写的程序或框架,如果不为每个任务指定一个线程,就不应该期望从虚拟线程中获得显著的好处。避免将同步、阻塞代码与异步框架混合使用。

将每个并发任务表示为一个虚拟线程;从不池化虚拟线程

关于虚拟线程,最难内化的是,尽管它们与平台线程具有相同的行为,但它们不应该表示相同的程序概念。

平台线程是稀缺的,因此是一种宝贵的资源。需要管理宝贵的资源,而管理平台线程的最常见方式是使用线程池。然后您需要回答的一个问题是,池中应该有多少个线程?

但虚拟线程是丰富的,因此每个线程都不应该代表一些共享的、池化的资源,而是一个任务。线程从托管资源变成应用程序域对象。我们应该有多少虚拟线程的问题变得显而易见,就像我们应该使用多少字符串在内存中存储一组用户名的问题一样:虚拟线程的数量总是等于应用程序中并发任务的数量。

将n个平台线程转换为n个虚拟线程几乎没有好处;相反,需要转换的是任务。

要将每个应用程序任务表示为线程,请不要像下面的示例那样使用共享线程池执行器:

Future<ResultA> f1 = sharedThreadPoolExecutor.submit(task1);
Future<ResultB> f2 = sharedThreadPoolExecutor.submit(task2);
// ... use futures

相反,使用虚拟线程执行器,如以下示例所示:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
   Future<ResultA> f1 = executor.submit(task1);
   Future<ResultB> f2 = executor.submit(task2);
   // ... use futures
}

代码仍然使用ExecutorService,但从Executors.newVirtualThreadPerTaskExecutor()返回的代码没有使用线程池。相反,它为每个提交的任务创建一个新的虚拟线程。

此外,ExecutorService本身是轻量级的,我们可以创建一个新的,就像处理任何简单的对象一样。这使我们能够依赖于新添加的ExecutorService.close()方法和try-with-resources构造。在try块结束时隐式调用的close方法将自动等待提交给ExecutorService的所有任务(即ExecutorServices派生的所有虚拟线程)终止。

对于输出场景,这是一个特别有用的模式,在输出场景中,您希望同时执行对不同服务的多个传出调用,如以下示例所示:

void handle(Request request, Response response) {
    var url1 = ...
    var url2 = ...
 
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        var future1 = executor.submit(() -> fetchURL(url1));
        var future2 = executor.submit(() -> fetchURL(url2));
        response.send(future1.get() + future2.get());
    } catch (ExecutionException | InterruptedException e) {
        response.fail(e);
    }
}
 
String fetchURL(URL url) throws IOException {
    try (var in = url.openStream()) {
        return new String(in.readAllBytes(), StandardCharsets.UTF_8);
    }
}

您应该创建一个新的虚拟线程,如上所示,用于即使是小的、短暂的并发任务。

为了获得更多关于编写输出模式和其他具有更好可观察性的常见并发模式的帮助,请使用结构化并发。

根据经验,如果您的应用程序从来没有10000个或更多的虚拟线程,那么它就不太可能从虚拟线程中受益。要么它的负载太轻,需要更好的吞吐量,要么您没有向虚拟线程表示足够多的任务。

使用信号量限制并发

有时需要限制某个操作的并发性。例如,某些外部服务可能无法处理超过十个并发请求。由于平台线程是一种宝贵的资源,通常在池中进行管理,线程池变得如此普遍,以至于它们被用于限制并发性,如以下示例所示:

ExecutorService es = Executors.newFixedThreadPool(10);
...
Result foo() {
    try {
        var fut = es.submit(() -> callLimitedService());
        return f.get();
    } catch (...) { ... }
}

此示例确保对有限服务最多有十个并发请求。

但限制并发只是线程池操作的副作用。池是为了共享稀缺资源而设计的,虚拟线程并不稀缺,因此永远不应该被池化!

在使用虚拟线程时,如果您想限制访问某些服务的并发性,则应该使用专门为此目的设计的构造:Semaphore类。以下示例演示了此类:

Semaphore sem = new Semaphore(10);
...
Result foo() {
    sem.acquire();
    try {
        return callLimitedService();
    } finally {
        sem.release();
    }
}

碰巧调用foo的线程将被抑制,也就是说,被阻塞,这样一次只有十个线程可以取得进展,而其他线程将不受阻碍地进行业务。

简单地用信号量阻塞一些虚拟线程似乎与将任务提交到固定线程池有很大不同,但事实并非如此。将任务提交给线程池会使它们排队等待稍后执行,但信号量在内部(或任何其他阻塞同步结构)创建一个被阻塞的线程队列,该队列反映了等待池线程执行它们的任务队列。由于虚拟线程是任务,因此生成的结构是等效的:

尽管您可以将平台线程池视为处理从队列中提取的任务的工作线程,将虚拟线程视为任务本身,直到它们可以继续,但计算机中的底层表示实际上是相同的。识别排队任务和阻塞线程之间的等效性将有助于充分利用虚拟线程。

数据库连接池本身就是一个信号灯。限制为十个连接的连接池将阻止第十一个线程尝试获取连接。不需要在连接池的顶部添加额外的信号量。

不要在线程本地变量中缓存昂贵的可重用对象

虚拟线程和平台线程一样支持线程本地变量。有关详细信息,请参阅线程本地变量(thread local variables)。通常,线程局部变量用于将一些特定于上下文的信息与当前运行的代码相关联,例如当前事务和用户ID。这种线程局部变量的使用对于虚拟线程来说是完全合理的。但是,请考虑使用更安全、更高效的作用域值。有关详细信息,请参阅作用域值。

线程局部变量的另一种用法与虚拟线程根本不同:缓存可重用对象。这些对象的创建成本通常很高(并且会消耗大量内存),是可变的,并且不是线程安全的。它们被缓存在线程局部变量中,以减少它们被实例化的次数和内存中的实例数量,但它们会被线程上不同时间运行的多个任务重用。

例如,SimpleDateFormat的实例的创建成本很高,而且不是线程安全的。出现的一种模式是将这样的实例缓存在ThreadLocal中,如以下示例所示:

static final ThreadLocal<SimpleDateFormat> cachedFormatter = 
       ThreadLocal.withInitial(SimpleDateFormat::new);

void foo() {
  ...
	cachedFormatter.get().format(...);
	...
}

只有当线程(以及缓存在线程本地中的昂贵对象)被多个任务共享和重用时,这种缓存才有帮助,就像平台线程被池化时一样。许多任务在线程池中运行时可能会调用foo,但由于线程池只包含几个线程,因此对象只会实例化几次——每个池线程一次——缓存并重用。

然而,虚拟线程从不被池化,也从不被不相关的任务重用。因为每个任务都有自己的虚拟线程,所以来自不同任务的每次对foo的调用都会触发新SimpleDateFormat的实例化。此外,由于可能有大量虚拟线程同时运行,因此昂贵的对象可能会消耗相当多的内存。这些结果与线程本地缓存所要实现的结果正好相反。

没有单一的通用替代方案可供选择,但在SimpleDateFormat的情况下,您应该将其替换为DateTimeFormatter。DateTimeFormatter是不可变的,因此所有线程都可以共享一个实例:

static final DateTimeFormatter formatter = DateTimeFormatter….;

void foo() {
  ...
	formatter.format(...);
	...
}

请注意,使用线程局部变量缓存共享的昂贵对象有时是由异步框架在幕后完成的,因为异步框架隐含地假设它们由极少数池线程使用。这就是为什么混合虚拟线程和异步框架不是一个好主意的原因之一:对方法的调用可能会导致在线程本地变量中实例化旨在缓存和共享的代价高昂的对象。

避免长时间和频繁的固定

当前虚拟线程实现的一个限制是,在同步块或方法内部执行阻塞操作会导致JDK的虚拟线程调度程序阻塞宝贵的操作系统线程,而如果阻塞操作在同步块和方法之外执行,则不会阻塞。我们称这种情况为“钉扎”。如果阻塞操作是长期且频繁的,则固定可能会对服务器的吞吐量产生不利影响。保护短暂的操作,如内存中的操作,或具有同步块或方法的不频繁操作,应该不会产生不利影响。

为了检测可能有害的钉扎实例,(JDK飞行记录器(JFR)会发出JDK。VirtualThreadPined线程当锁定了阻塞操作时;默认情况下,当操作耗时超过20ms时,会启用此事件。

或者,可以使用系统属性jdk.tracePinnedThreads在线程被固定时阻塞时发出堆栈跟踪。使用选项Djdk.tracePinnedThreads=full运行时,当线程在固定时发生阻塞时,将打印完整的堆栈跟踪,突出显示本地帧和持有监视器的帧。使用选项Djdk.tracePinnedThreads=short运行会将输出仅限于有问题的帧。

如果这些机制检测到固定既长时间又频繁的位置,请在这些特定位置使用synchronized with ReentrantLock来替换(同样,在保护短时间或不频繁操作的位置,无需替换synchronized)。以下是同步化块的长期频繁使用示例。

synchronized(lockObj) {
    frequentIO();
}
lock.lock();
try {
    frequentIO();
} finally {
    lock.unlock();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/469147.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【一】【单片机】有关LED的实验

点亮一个LED灯 根据LED模块原理图&#xff0c;我们可以知道&#xff0c;通过控制P20、P21...P27这八个位置的高低电平&#xff0c;可以实现D1~D8八个LED灯的亮灭。VCC接的是高电平&#xff0c;如果P20接的是低电平&#xff0c;那么D1就可以亮。如果P20接的是高电平&#xff0c;…

CSS基础属性(学习笔记)

一、CSS介绍 CSS即层叠样式表/级联样式表&#xff0c;简称样式表 html&#xff1a;写网页结构内容 css&#xff1a;写网页样式 实现了内容与表现的分离&#xff0c;提高了代码的重用性和维护性 CSS注释不被浏览器解析&#xff0c;给开发人员一个标注 快捷键&#xff1a;ctrl/ 语…

YOLOv5独家改进:block改进 | RepViTBlock和C3进行结合实现二次创新 | CVPR2024清华RepViT

💡💡💡本文独家改进:CVPR2024 清华提出RepViT:轻量级新主干!从ViT角度重新审视移动CNN,RepViTBlock和C3进行结合实现二次创新 改进结构图如下: 收录 YOLOv5原创自研 https://blog.csdn.net/m0_63774211/category_12511931.html 💡💡💡全网独家首发创…

FTP文件传输协议

FTP 文章目录 FTP1. ftp简介2. ftp架构3. ftp数据连接模式4. 用户认证5. vsftpd5.1 vsftpd安装5.2 配置匿名用户ftp5.2.1上传&#xff08;下面使用的是FileZilla软件&#xff09;5.2.2下载5.2.3创建5.2.4删除 5.3配置本地&#xff08;系统&#xff09;用户ftp5.3.1上传5.3.2下载…

Qt教程 — 3.4 深入了解Qt 控件:Input Widgets部件(3)

目录 1 Input Widgets简介 2 如何使用Input Widgets部件 2.1 Dial 组件-模拟车速表 2.2 QScrollBar组件-创建水平和垂直滚动条 2.3 QSlider组件-创建水平和垂直滑动条 2.4 QKeySequenceEdit组件-捕获键盘快捷键 Input Widgets部件部件较多&#xff0c;将分为三篇文章介绍…

网络基础知识-DNS与DHCP+网络规划与设计故障诊断+嵌入式系统设计师备考笔记

0、前言 本专栏为个人备考软考嵌入式系统设计师的复习笔记&#xff0c;未经本人许可&#xff0c;请勿转载&#xff0c;如发现本笔记内容的错误还望各位不吝赐教&#xff08;笔记内容可能有误怕产生错误引导&#xff09;。 本章的主要内容见下图&#xff1a; 本章知识和计算机…

创意二维码营销案例:帕森斯设计学院在巴黎市中心搭建“沙滩度假地”

作为一个专业的艺术设计学院&#xff0c;帕森斯设计学院&#xff08;Parsons School of Design, The New School&#xff09;以其卓越的教学质量和创新的设计理念享誉全球。 每年的夏天&#xff0c;帕森斯设计学院都会举办一个暑期短期项目&#xff0c;面向全球学生&#xff0…

AI时代,Matter如何融入与服务中国智能家居市场,助力中国企业出海?

随着智能家居产业的飞速发展&#xff0c;丰富多样的智能家居产品为消费者带来了便利的同时&#xff0c;因为不同品牌、不同产品之间的协议与标准不统一&#xff0c;导致消费者体验产生割裂&#xff0c;本来想买个“智能”家居&#xff0c;结果买了个“智障”家居&#xff0c;这…

Qt学习--多态(虚函数)

这次来分享多态的概念&#xff0c;这是比较重要的知识点 面向对象的三大特征&#xff1a;封装、继承、多态 首先&#xff1a;来点官方术语&#xff1a; 多态&#xff0c;通俗来讲就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出…

软考88-上午题-【操作系统】-进程的状态及状态间的切换

一、三态模型 多道程序系统&#xff1a; 在单道程序系统中&#xff0c;计算机内存中只允许一个程序运行&#xff0c;而多道程序系统则允许多个程序同时运行&#xff0c;从而大大提高了系统的整体性能。 通过允许多个程序同时运行和共享资源&#xff0c;多道程序设计技术使得操作…

使用uniapp,uni-data-select组件时,内容长度没超过容器宽度时候虽然能显示全内容但是数据后边会出现三个点,逼死强迫症

项目场景&#xff1a; 微信小程序开发&#xff0c;使用uniapp&#xff0c;uni-data-select组件时&#xff0c;内容长度没超过容器宽度时候虽然能显示全内容但是数据后边会出现三个点&#xff0c;逼死强迫症 解决方案&#xff1a; 找到组件的源代码&#xff0c;然后删除那三个…

layui2.9.7-入门初学

下载&#xff1a;https://layui.dev/ 下载后解压&#xff1a; 在hbuider中新建一个项目 将如上解压好的文件打开&#xff0c;复制如下到项目中 写案例&#xff0c;基础学习通之前的bootstrap 那样&#xff0c;挨个相中哪个就测试哪个&#xff0c;在这里不再重复罗列&#x…

windows跳板机配置(端口转发)

目录 前言操作步骤端口防火墙开放测试参考 前言 跳板机一般用于异构网络间的中转站&#xff0c;比如对方在防火墙上只给你开放了一台服务器的权限&#xff0c;你无法访问对方局域网的其它主机&#xff0c;但你能访问的这台服务器则有权限访问其它主机。那么这台服务器就可以作…

Docker 从0安装 nacos集群

前提条件 Docker支持一下的CentOs版本 Centos7(64-bit)&#xff0c;系统内核版本为 3.10 以上Centos6.5(64-bit) 或者更高版本&#xff0c;系统内核版本为 2.6.32-431 或者更高版本 安装步骤 使用 yum 安装&#xff08;CentOS 7下&#xff09; 通过 uname -r 命令查看你当…

Go web 基础相关知识

Go web Web工作方式 浏览器本身是一个客户端&#xff0c;当你输入URL的时候&#xff0c;首先浏览器会去请求DNS服务器&#xff0c;通过DNS获取相应的域名对应的IP&#xff0c;然后通过IP地址找到IP对应的服务器后&#xff0c;要求建立TCP连接&#xff0c;等浏览器发送完HTTP …

通过nginx+xray服务搭建及本地配置

一、xray服务配置 下载&#xff1a;https://github.com/XTLS/Xray-core 进入下载界面 这里我选择的是Xray-linux-64.zip 将文件解压到 /usr/local/xray 编辑配置文件/usr/local/xray/config.json uuid可以在v2ray客服端自动生成&#xff0c;也可以在UUID v4 生成器 - KKT…

高性能 MySQL 第四版(GPT 重译)(二)

第四章&#xff1a;操作系统和硬件优化 你的 MySQL 服务器的性能只能和它最弱的环节一样好&#xff0c;而运行 MySQL 的操作系统和硬件通常是限制因素。磁盘大小、可用内存和 CPU 资源、网络以及连接它们的所有组件都限制了系统的最终容量。因此&#xff0c;你需要仔细选择硬件…

腾讯云服务器多少钱一年?听完你可别后悔!

腾讯云服务器多少钱一年&#xff1f;61元一年起。2024年最新腾讯云服务器优惠价格表&#xff0c;腾讯云轻量2核2G3M服务器61元一年、2核2G4M服务器99元一年可买三年、2核4G5M服务器165元一年、3年756元、轻量4核8M12M服务器646元15个月、4核16G10M配置32元1个月、312元一年、8核…

考研数学|张宇还是武忠祥?怎么选?

我觉得张宇老师和武忠祥老师讲课实力都差不多&#xff0c;区别就在于风格的不同 张宇老师的讲课风格比较活泼&#xff0c;擅长调动学生的思维跟着课堂一起走&#xff0c;并且张宇老师发明了很多容易记的段子&#xff0c;但是虽然张宇老师段子多&#xff0c;一点也不妨碍他讲课…

网站制作基本流程|新手必收藏

网站制作基本流程|新手必收藏 我们选择了白嫖雨云的二级域名 浏览器输入https://www.rainyun.com/z22_ 创建账号然后选择一个你喜欢的子域名我建议后缀选择ates.top的 选择自定义地址&#xff0c;类型选择cname 现在要选择记录值了&#xff0c;有a&#xff0c;aa&#xff0c;tx…