Java虚拟线程vs平台线程

本文探讨了Java中经典线程模型的局限性,引入了虚拟线程和平台线程的概念,强调了虚拟线程在提高资源利用率和简化并发编程方面的优势。同时,指出了ThreadLocal在虚拟线程环境下的变化和可能带来的问题。

本文讨论一些关于Java线程的概念:

  • 虚拟线程 Virtual Thread
  • 平台线程 Platform Thread

将介绍虚拟线程和平台线程的本质区别以及它们如何提升级程序性能。

在这里插入图片描述

经典线程模型

当Java Web Server处理HTTP请求时:

  1. 创建Thread并准备接收请求
  2. 请求来到匹配到一个Thread,然后Thread做访问数据库的处理
  3. Thread会等待数据库返回数据
  4. 取得数据后响应请求,最后回到线程池中

在这里插入图片描述

在上面的线程生命周期中,第3步线程只是等待,不会做任何事情。这是一大缺陷,并没有充分利用系统资源。线程大多数时间只是在等待,并没有做实际的工作。

在Java19以前,创建的线程我们叫本地线程(Native Threads)或者平台线程(Platform Thread)。在这种架构方式下,平台线程和操作系统线程(OS Thread)是一对一的关系。这意味着,操作系统线程没有被充分利用。使得线程的使用很重,成本提高。

虚拟线程 (Virtual Threads)

Java的虚拟线程是多线程处理并发任务的重大改进。虚拟线程是作为 Project Loom一部分,为了编写、维护以及观察高吞吐量的并发程序而设计的。它让开发者很容易写出轻量级的并发程序。

虚拟线程是由Java Virtual Machine(JVM)管理的轻量级线程,而不是由操作系统管理。不像平台线程,虚拟线程的创建和销毁成本很低。它们会被对应到更少数量的平台线程。使Java可以使用更少的机器资源,处理成千上万、上百万的并发任务。

在这里插入图片描述

虚拟线程比平台线程更实用

虚拟线程有很多优点。首先,它可以更有效的利用操作系统资源。因为系统线程是轻量级的,与平台线程相比,消耗更少的内存和CPU资源。这使用可以在一个JVM中执行数量非常多的并发任务。

第二,虚拟线程简化了Java并发编程。它允许开发者以直接的命令式的风格编写代码,像想是在写同步的代码一样。不用处理异步编程的复杂性。这种减化也会减少BUG的出现。

而且,虚拟线程能让CPU更好的被利用。在传统的线程模型中,大量的CPU时间被浪费在大量的线程管理和切换线程的工作中,虚拟线程减少了这些浪费,让并发任务的执行更加有效。

实践

如果我们使用经典的平台线程去完成工作,我们要像下面这样编写程序:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PlatformThreadDemo {
    private static final Logger logger = LoggerFactory.getLogger(PlatformThreadDemo.class);

    public static void main(String[] args) {
        attendMeeting().start();
        completeLunch().start();
    }

    private static Thread attendMeeting(){
        var message = "Platform Thread [Attend Meeting]";
        return new Thread(() -> {
            logger.info(STR."{} | \{message}", Thread.currentThread());
        });
    }

    private static Thread completeLunch(){
        var message = "Platform Thread [Complete Lunch]";
        return new Thread(() -> {
            logger.info(STR."{} | \{message}", Thread.currentThread());
        });
    }

    // using builder pattern to create platform threads
    private static void attendMeeting1(){
        var message = "Platform Thread [Attend Meeting]";
        Thread.ofPlatform().start(() -> {
            logger.info(STR."{} | \{message}", Thread.currentThread());
        });
    }

    private static void completeLunch1(){
        var message = "Platform Thread [Complete Lunch]";
        Thread.ofPlatform().start(() -> {
            logger.info(STR."{} | \{message}", Thread.currentThread());
        });
    }
}

上面的代码用两种方式创建平台线程:

  1. 使用Thread的构造方法,传一个实现Runnable接口的lambda表达式进去。
  2. 使用Thread的ofPlatform()构建方法

再来看一下更复杂的虚拟线程:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class DailyRoutineWorkflow {
    static final Logger logger = LoggerFactory.getLogger(DailyRoutineWorkflow.class);

    static void log(String message) {
        logger.info(STR."{} | \{message}", Thread.currentThread());
    }


    private static void sleep(Long duration) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(duration);
    }

    private static Thread virtualThread(String name, Runnable runnable) {
        return Thread.ofVirtual().name(name).start(runnable);
    }

    static Thread attendMorningStatusMeeting() {
        return virtualThread(
                "Morning Status Meeting",
                () -> {
                    log("I'm going to attend morning status meeting");
                    try {
                        sleep(1000L);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    log("I'm done with morning status meeting");
                });
    }

    static Thread workOnTasksAssigned() {
        return virtualThread(
                "Work on the actual Tasks",
                () -> {
                    log("I'm starting my actual work on tasks");
                    try {
                        sleep(1000L);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    log("I'm done with actual work on tasks");
                });
    }

    static Thread attendEveningStatusMeeting() {
        return virtualThread(
                "Evening Status Meeting",
                () -> {
                    log("I'm going to attend evening status meeting");
                    try {
                        sleep(1000L);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    log("I'm done with evening status meeting");
                });
    }

    static void concurrentRoutineExecutor() throws InterruptedException {
        var morningMeeting = attendMorningStatusMeeting();
        var actualWork = workOnTasksAssigned();
        var eveningMeeting = attendEveningStatusMeeting();
        morningMeeting.join();
        actualWork.join();
        eveningMeeting.join();
    }
}

上面的代码展示了如何用工厂方法创建虚拟线程。除了工厂方法外,我们还可以使用为虚拟线程java.util.concurrent.ExecutorService定制的子类java.util.concurrent.ThreadPerTaskExecutor。可以像下面这样使用ExecutorService

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class DailyRoutineWorkflowUsingExecutors {
    static final Logger logger = LoggerFactory.getLogger(DailyRoutineWorkflowUsingExecutors.class);

    static void log(String message) {
        logger.info(STR."{} | \{message}", Thread.currentThread());
    }


    private static void sleep(Long duration) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(duration);
    }

    public static void executeJobRoute() throws ExecutionException, InterruptedException {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            var morningMeeting = executor.submit(() -> {
                log("I'm going to attend morning status meeting");
                try {
                    sleep(1000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log("I'm done with morning status meeting");
            });

            var actualWork = executor.submit(() -> {
                log("I'm starting my actual work on tasks");
                try {
                    sleep(1000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log("I'm done with actual work on tasks");
            });

            var eveningMeeting = executor.submit(() -> {
                log("I'm going to attend evening status meeting");
                try {
                    sleep(1000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log("I'm done with evening status meeting");
            });

            morningMeeting.get();
            actualWork.get();
            eveningMeeting.get();
        }
    }
}

分析输出结果

如果运行上面代码,会得到类似下面的日志:
在这里插入图片描述
日志被“|”分成了两部分。第一部分像这样VirtualThread[#26]/runnable@ForkJoinPool-1-worker-3。告诉我们VirtualThread[#26]这个虚拟线程运行在ForkJoinPool-1-worker-3这个平台线程上。

ThreadLocal和虚拟线程

在Java中,ThreadLocal是一种让每个线程保存不同对象的机制。每个线程访问ThreadLocal变量,都会线程各自对应的值。修改ThreadLocal变量,不会影响其它线程中的值。ThreadLocal被用于维持各自线程的状态,比如用户的Session、数据库连接。

但是,与虚拟线程一起使用时,ThreadLocal的行为有很大的改变。虚拟线程是由JVM管理的轻量级线程,会被大量调度,不像传统的平台线程,与操作系统线程一对一绑定。

由于 VirtualThreads 可以创建数百万个,因此创建和使用 ThreadLocal 会造成内存泄漏。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo {
    private static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

    static final Logger logger = LoggerFactory.getLogger(DailyRoutineWorkflow.class);

    static void log(String message) {
        logger.info(STR."{} | \{message}", Thread.currentThread());
    }

    private static void sleep(Long duration) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(duration);
    }

    public static void virtualThreadContext() throws InterruptedException {
        var virtualThread1 = Thread.ofVirtual().name("thread-1").start(() -> {
            stringThreadLocal.set("thread-1");
            try {
                sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log(STR."thread name is \{stringThreadLocal.get()}");
        });
        var virtualThread2 = Thread.ofVirtual().name("thread-2").start(() -> {
            stringThreadLocal.set("thread-2");
            try {
                sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log(STR."thread name is \{stringThreadLocal.get()}");
        });
        virtualThread1.join();
        virtualThread2.join();
    }
}

结尾

本文介绍了Java的线程模型、虚拟线程、平台线程、ThreadLocal的问题。揭示了Java并发编程的发展现状。虚拟线程的出现改变了游戏规则。与平台线程相比,更轻量、更有效率。使Java可以使用最少的资源消耗,处理大量的并发任务。简化了编程模型,加强了程序的可扩展性。

然而,在这种新情况下使用 ThreadLocal 需要仔细考虑。在传统线程中,ThreadLocal 仍然是维护线程数据的强大工具。但在虚拟线程中,它的应用变得更加复杂,因此需要采用其他策略来管理状态和上下文。为开发人员构建响应式、可扩展和更高效的应用程序打开了新的大门。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值