wenmo8 发布的文章

.NET 9 针对云原生开发进行了显著优化,重点改进了分布式追踪、HTTP/3 支持和容器镜像优化等方面。

这些特性极大地提升了 .NET 在现代云原生应用中的适配性与开发效率。

1. 设计原理

1.1 分布式追踪支持

背景
:在云原生架构中,应用程序通常由多个微服务组成。分布式追踪能够帮助开发者监控和诊断跨服务的调用链路,快速定位性能瓶颈和故障点。

实现
:.NET 9 内置了对 OpenTelemetry 的支持,提供标准化的接口,方便开发者将追踪数据输出到不同的追踪系统,如 Jaeger、Zipkin 或 Azure Monitor。

目标
:简化分布式追踪的集成过程,提高应用程序的可观测性,增强故障诊断和性能调优的能力。

1.2 HTTP/3 改进

背景
:HTTP/3 基于 QUIC 协议,具有低延迟和高传输效率的特点,特别适用于高并发和实时性要求高的应用场景。

实现
:.NET 9 的
HttpClient
和 Kestrel(内置的 Web 服务器)原生支持 HTTP/3,开发者可以直接利用这一特性,无需额外配置。

目标
:提升网络通信的性能和可靠性,为现代 Web 应用和微服务架构提供更高效的传输协议支持。

1.3 容器镜像优化

背景
:在云原生环境中,应用程序通常以容器形式部署。容器镜像的大小和启动速度直接影响部署效率和资源利用率。

实现
:.NET 9 提供了更小的基础镜像,采用精简的操作系统层,减少不必要的组件。此外,通过改进发布流程,支持 ReadyToRun(R2R)和 AOT(Ahead-Of-Time)编译,缩短应用启动时间。

目标
:降低容器镜像的体积,加快启动速度,提高部署效率,减少资源消耗。

2. 示例代码

分布式追踪示例

usingSystem.Diagnostics;usingOpenTelemetry;usingOpenTelemetry.Trace;var builder =WebApplication.CreateBuilder(args);//配置 OpenTelemetry 追踪
builder.Services.AddOpenTelemetryTracing(tracingBuilder =>{
tracingBuilder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddConsoleExporter();
//将追踪数据输出到控制台 });var app =builder.Build();

app.MapGet(
"/", async context =>{using var activity = new ActivitySource("MyApp").StartActivity("ProcessingRequest");
activity
?.SetTag("http.method", "GET");
activity
?.SetTag("http.url", context.Request.Path);await context.Response.WriteAsync("Hello, World!");
});

app.Run();

HTTP/3 示例

var builder =WebApplication.CreateBuilder(args);//启用 HTTP/3
builder.WebHost.ConfigureKestrel(serverOptions =>{
serverOptions.ListenAnyIP(
5000, listenOptions =>{
listenOptions.Protocols
=Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2AndHttp3;
});
});
var app =builder.Build();

app.MapGet(
"/", () => "Hello, HTTP/3!");

app.Run();

容器优化示例

Dockerfile

# 使用 .NET 9精简版基础镜像
FROM mcr.microsoft.com
/dotnet/runtime-deps:9.0AS base
WORKDIR
/app

FROM mcr.microsoft.com
/dotnet/sdk:9.0AS build
WORKDIR
/src
COPY . .
RUN dotnet publish
-c Release -o /app --self-contained true /p:PublishTrimmed=true /p:PublishAot=trueFROM base AS final
WORKDIR
/app
COPY
--from=build /app .
ENTRYPOINT [
"./MyApp"]

3. 应用场景

  1. 微服务架构
    :利用分布式追踪监控服务间的调用链路,快速定位问题;通过 HTTP/3 提升服务间通信效率;使用优化后的容器镜像实现快速部署和扩展。

  2. 实时通信应用
    :如在线游戏、视频会议等,对低延迟要求高的应用,HTTP/3 的改进能够显著提升用户体验。

  3. 资源受限环境
    :在边缘计算或物联网设备上,精简的容器镜像和快速启动能力能够有效降低资源占用,提高运行效率。

  4. 高并发 Web 应用
    :通过 HTTP/3 的多路复用特性,提升网页加载速度和响应能力,改善用户体验。

4. 高阶应用

  1. 自定义分布式追踪集成
    :结合 OpenTelemetry,开发者可以自定义追踪策略,将数据输出到特定的监控系统,满足不同的运维需求。

  2. 结合 gRPC 和 HTTP/3
    :利用 HTTP/3 的优势,提升 gRPC 的传输效率,构建高性能的服务间通信机制。

  3. 无服务器架构
    :通过 AOT 编译和精简的容器镜像,将应用部署在无服务器平台上,实现快速启动和高效运行。

  4. 性能监控与优化
    :使用分布式追踪数据,结合性能分析工具,深入了解应用的运行状况,进行针对性的优化,提高整体性能。

通过这些优化,.NET 9 为云原生应用开发提供了更强大的支持,帮助开发者构建高性能、高可用的现代应用程序。

周国庆

2024/12/31

1、认识 Simple Binary Encoding (sbe)

高性能Java库 Agrona 的主要目标是减少性能瓶颈,通过提供线程安全的直接和原子缓冲区、无装箱操作的原始类型列表、开散列映射和集合以及锁-free队列等,为开发者在处理并发和低延迟场景时提供强大工具。

Simple Binary Encoding (sbe) 是 Agrona 的一部分,也是高性能通讯框架 Aeron 的一部分。

2、什么是 rpc ?

一讲 rpc ,很多人会想到 dubbo (国产)和 grpc。估计还会联想到注册与发现服务;可能还会联想到微服务。可能就会觉得这个事儿“老重啦”,害怕!

其实很简单的,你请求一次 http 就是个 rpc 请求了(远程过程调用嘛)。最典型的就是 http + json 请求了。

3、现在讲 httputils + sbe

这里我们会用到两个重要的
solon 框架
的插件:一个是 httputils 工具插件,一个是 abc + agrona 序列化插件(abc 适配了多个编解码方案)。

<!-- 这是 sbe 的编解码包装器 -->
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-serialization-abc</artifactId>
</dependency>

<dependency>
    <groupId>org.agrona</groupId>
    <artifactId>agrona</artifactId>
    <version>${agrona-sbe.version}</version>
</dependency>

<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-net-httputils</artifactId>
</dependency>

这里要感谢 solon 框架,它强调
三元合一(mvc 与 rpc 是自然一体的)
。下面,开始干活啦...

  • 公用包(也可以在客户端,服务端分别定义实体类。只要实现 SbeSerializable 接口即可 )

这里定义一个 sbe 实体类。注意要实现 SbeSerializable 接口。

@Getter
@Setter
public class MessageDo implements SbeSerializable {
    private long id;
    private String title;

    @Override
    public void serializeRead(SbeInput in) {
        id = in.readLong();
        title = in.readString();
    }

    @Override
    public void serializeWrite(SbeOutput out) {
        out.writeLong(id);
        out.writeString(title);
    }
}
  • 服务端(只支持 @Body 数据接收,只支持实体类)

在 solon web 项目里,添加一个控制器(注解可以用
@Remoting

@Controller
)。使用
@Remoting
时,方法上不需要加
@Mapping
注解。

#添加插件
org.noear:solon-web
org.noear:solon-serialization-abc
org.agrona:agrona:${agrona-sbe.version} # 提供 sbe 序列化支持
@Mapping("/rpc/demo")
@Remoting
public class HelloServiceImpl {
    @Override
    public MessageDo hello(@Body MessageDo message) { //还可接收路径变量,与请求上下文
        return message;
    }
}
  • 客户端应用 for HttpUtils(只支持 body 数据提交,只支持实体类)
#添加插件
org.noear:solon-net-httputils
org.noear:solon-serialization-abc
org.agrona:agrona:${agrona-sbe.version} # 提供 sbe 序列化支持
//应用代码
@Component
public class DemoCom {
    public MessageDo hello() {
        MessageDo message = new MessageDo();
        message.setId(3);
        
        //指明请求数据为 ABC,接收数据要 ABC
        return HttpUtils.http("http://localhost:8080/rpc/demo/hello")
                .serializer(AbcBytesSerializer.getInstance())
                .header(ContentTypes.HEADER_CONTENT_TYPE, ContentTypes.ABC_VALUE)
                .header(ContentTypes.HEADER_ACCEPT, ContentTypes.ABC_VALUE)
                .bodyOfBean(message)
                .postAs(MessageDo.class);
    }
}

4、总结

总体上,跟 json 没什么大的区别。主要是指定了:序列化器、内容类型、接收类型,让各端能识别类据类型。

5、还可以使用“注解式 http 客户端”框架

肯定也会有人觉得,一个接口还好,如果有很多接口就要写很多重复的http请求代码了。所以,“注解式 http 客户端” 很重要,这也是很多 rpc 框架流行的原因,就像调用本地接口一样,使用远程接口。

nami 是 solon 框架的 rpc 客户端(或者,注解式 http 客户端),支持各种序列化。(只要是“支持序列化定制”的注解式 http 客户端,都可用!)

  • 添加两个依赖包
#添加插件
org.noear:nami-coder-abc # abc 编解码支持
org.noear:nami-channel-http     # http 请求通道支持,也可以是 socketd(支持 tcp, udp, ws)
org.agrona:agrona:${agrona-sbe.version} # 提供 sbe 序列化支持
  • 代码应用(只支持 body 数据提交,只支持实体类)
@NamiClient(url = "http://localhost:8080/rpc/demo", headers = {ContentTypes.ABC, ContentTypes.ABC_ACCEPT})
public interface HelloService {
    MessageDo hello(@NamiBody MessageDo message);
    //方法2
    //方法3
    //方法4
    //方法5
    //方法6
}

@Component
public class DemoCom {
    @NamiClient //注入
    HelloService helloService;
  
    public MessageDo hello() {
         MessageDo message = new MessageDo();
         message.setId(3);
        
         rerturn helloService.hello(message);
    }
}

雪花算法的唯一性,在单个节点中是可以保证的,对应kubernetes中的应用,如果是横向扩展后,进行多副本的情况下,可能出现重复的ID,这需要我们按着pod_name进行一个workId的生成,我还是建议通过
不引入第三方组件
和网络请求的前提下解决这个问题,所以我修改了kubernetes的yaml文件。

  • k8s的yaml配置
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-container
        image: my-image:latest
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name  # 获取当前 Pod 的名称

  • 字符串(0~1024)数字方法,通过掩码的方式
public static int stringToNumber(String input) {
        // 使用CRC32计算字符串的哈希值
        CRC32 crc = new CRC32();
        byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
        crc.update(bytes);
        
        // 获取哈希值并限制在0到1023之间
        long hashValue = crc.getValue();
        return (int) (hashValue % 1024);
    }
  • 获取服务器机器码
/**
	 * 获取机器码.
	 * @return
	 */
	public static String getUniqueMachineId() {
		StringBuilder uniqueId = new StringBuilder();

		try {
			// 获取本机的IP地址
			InetAddress localHost = InetAddress.getLocalHost();
			uniqueId.append(localHost.getHostAddress()).append("_");

			// 获取网络接口并获取MAC地址
			Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
			while (networkInterfaces.hasMoreElements()) {
				NetworkInterface networkInterface = networkInterfaces.nextElement();
				byte[] mac = networkInterface.getHardwareAddress();
				if (mac != null) {
					for (int i = 0; i < mac.length; i++) {
						uniqueId.append(String.format("%02X", mac[i]));
						if (i < mac.length - 1) {
							uniqueId.append("-");
						}
					}
					uniqueId.append("_");
				}
			}

			// 添加系统信息作为补充
			String osName = System.getProperty("os.name");
			String osVersion = System.getProperty("os.version");
			String userName = System.getProperty("user.name");
			uniqueId.append(osName).append("_").append(osVersion).append("_").append(userName);

		}
		catch (Exception e) {
			e.printStackTrace();
		}

		return uniqueId.toString();
	}
  • ID生成器的改进
@Slf4j
public class IdUtils {

	static SnowFlakeGenerator snowFlakeGenerator;

	public static String generateId() {
		if (snowFlakeGenerator == null) {
			long podNameCode = stringToNumber(Optional.ofNullable(System.getenv("POD_NAME")).orElse(stringToNumber(getUniqueMachineId())));
			log.debug("podNameCode:{}", podNameCode);
			snowFlakeGenerator = new SnowFlakeGenerator(podNameCode);

		}
		return snowFlakeGenerator.hexNextId();
	}

一、内存缓存(Memory Caching)

1. 具体的原理

内存缓存是一种在应用程序内存中存储数据的机制,旨在减少对外部数据源的频繁访问,从而提高应用程序的性能。

在 .NET 中,
MemoryCache
类提供了内存缓存的实现。它允许开发者将经常使用的数据存储在内存中,以便快速检索。

在 .NET 9 中,
MemoryCache
的实现是
ConcurrentDictionary<TKey,TValue>
的包装器,提供了功能丰富的 API。

2. 设计初衷

内存缓存的设计初衷是为了提高应用程序的性能,特别是在需要频繁访问不经常变化的数据时。

通过将这些数据存储在内存中,可以减少对数据库或其他外部数据源的访问次数,从而降低延迟和资源消耗。

这对于需要高性能和快速响应的应用程序尤为重要。

3. 应用场景

内存缓存适用于以下场景:

  • 频繁读取的静态数据
    :例如,应用程序配置、常量列表等,这些数据不经常变化,但需要频繁读取。

  • 计算成本高的数据
    :一些数据的计算过程复杂且耗时,将其结果缓存可以提高性能。

  • 会话状态管理
    :在某些情况下,可以使用内存缓存来存储用户的会话信息。

4. 示例代码

以下是使用
MemoryCache
的示例代码:

usingMicrosoft.Extensions.Caching.Memory;usingSystem;public classCacheExample
{
private readonlyIMemoryCache _cache;publicCacheExample(IMemoryCache cache)
{
_cache
=cache;
}
public string GetOrSetCacheItem(stringkey)
{
if (!_cache.TryGetValue(key, out stringcacheValue))
{
//数据不在缓存中,执行获取数据的逻辑 cacheValue = "获取的数据";//设置缓存选项 var cacheEntryOptions = newMemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(
5)); //设置滑动过期时间//将数据存储到缓存中 _cache.Set(key, cacheValue, cacheEntryOptions);
}
returncacheValue;
}
}

在上述代码中,
GetOrSetCacheItem
方法尝试从缓存中获取数据,如果不存在,则获取数据并将其添加到缓存中,设置滑动过期时间为 5 分钟。

二、集合(Collections)优化

1. 具体的原理

在 .NET 9 中,对集合的优化主要体现在
性能提升和内存使用
的改进上。

例如,对循环的优化,通过将向上计数的循环转换为向下计数的循环,减少指令数量,从而提高性能。

此外,还引入了循环中的强度降低优化,将昂贵的操作替换为更便宜的操作,减少对迭代变量的依赖。

2. 设计初衷

这些优化的设计初衷是为了提高集合操作的效率,减少不必要的计算和内存分配,从而提升应用程序的整体性能。

特别是在处理大量数据或频繁操作集合的场景下,这些优化能够显著减少 CPU 和内存的消耗。

3. 应用场景

集合优化适用于以下场景:

  • 大数据量处理
    :当需要处理大量数据时,优化的集合操作可以提高处理速度。

  • 高性能要求的应用
    :对于对性能有严格要求的应用程序,优化的集合操作可以减少延迟。

  • 实时系统
    :在需要实时响应的系统中,集合优化可以确保系统的及时性。

4. 示例代码

以下是一个使用向下计数循环的示例代码:

public int SumArray(int[] array)
{
int sum = 0;for (int i = array.Length - 1; i >= 0; i--)
{
sum
+=array[i];
}
returnsum;
}

在上述代码中,循环从数组的末尾向前遍历,减少了比较操作的指令数量,从而提高了性能。

以上是.NET 9 new features-Memory Caching 和 Collections 优化的研究和分享。

周国庆

2024/12/30

前言

在Java开发中,
线程
是并发编程中的核心工具。

无论是为了提高程序运行效率,还是为了处理复杂的并发任务,我们都需要在代码中使用线程。

但如果你只知道
Thread

Runnable
两种方式,那可就有点落后了。

其实,Java 提供了多种方式来创建线程,每一种都有其独特的优势和适用场景。

这篇文章将从浅入深,详细剖析
Java 创建线程的8种方法
,每种方法都会附带示例代码和场景解析,帮你彻底掌握线程的创建与管理,希望对你会有所帮助。

(我最近开源了一个基于 SpringBoot+Vue+uniapp 的商城项目,欢迎访问和star。)[
https://gitee.com/dvsusan/susan_mall
]

1. 继承 Thread 类

直接继承
Thread
类,重写
run()
方法,将任务逻辑写在
run()
中。

通过调用
start()
方法启动线程。

示例代码

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 正在执行任务");
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start(); // 启动线程
        thread2.start();
    }
}

场景解析

继承
Thread
是最简单的方式,非常适合初学者学习线程的基本原理。但这种方式扩展性差,因为 Java 是单继承语言,继承了
Thread
后就不能再继承其他类。

优缺点

  • 优点:
    简单直观,适合小型任务。
  • 缺点:
    限制了类的继承,无法复用已有的逻辑。

2. 实现 Runnable 接口

实现
Runnable
接口,将任务逻辑写在
run()
方法中。

通过
Thread
构造方法将
Runnable
对象传入,启动线程。

示例代码

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 正在执行任务");
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());
        thread1.start();
        thread2.start();
    }
}

场景解析

相比继承
Thread
,实现
Runnable
接口更灵活,避免了单继承的限制。大多数开发场景中,更推荐使用这种方式。

优缺点

  • 优点:
    解耦任务逻辑和线程对象,灵活性更高。
  • 缺点:
    需要额外创建
    Thread
    对象。

3. 实现 Callable 接口

Callable
接口是 Java 5 引入的,类似于
Runnable
,但它支持返回值,并可以抛出异常。

示例代码

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "线程名称:" + Thread.currentThread().getName() + ",任务执行完成";
    }
}

public class CallableExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
        Thread thread = new Thread(futureTask);
        thread.start();

        // 获取线程返回结果
        System.out.println("线程返回结果:" + futureTask.get());
    }
}

场景解析

如果你的线程需要返回结果,
Callable
是更好的选择,比如数据查询、复杂计算等场景。

优缺点

  • 优点:
    支持返回值和异常处理,功能更强大。
  • 缺点:
    代码复杂度比
    Runnable
    略高。

4. 使用线程池

线程池是一种高效的线程管理机制,可以复用线程,减少创建和销毁线程的开销。

示例代码

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        Runnable task = () -> System.out.println("线程名称:" + Thread.currentThread().getName() + " 正在执行任务");

        for (int i = 0; i < 5; i++) {
            executorService.execute(task);
        }

        executorService.shutdown();
    }
}

场景解析

适用于需要高并发处理任务的场景,比如 Web 服务的请求处理。

优缺点

  • 优点:
    高效管理线程生命周期,避免频繁创建和销毁线程。
  • 缺点:
    需要合理配置线程池参数,否则可能导致资源浪费。

5. 使用 ScheduledExecutorService

ScheduledExecutorService
是 Java 提供的一种定时任务调度机制,可以在指定时间点或周期性地执行任务。

示例代码

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

public class ScheduledExample {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        Runnable task = () -> System.out.println("当前时间:" + System.currentTimeMillis());

        // 延迟1秒后,每2秒执行一次
        scheduler.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);

        // 程序运行一段时间后需要手动关闭线程池
        // scheduler.shutdown();
    }
}

场景解析

适用于周期性任务,比如定时备份、定时清理缓存等。

优缺点

  • 优点:
    易于实现定时和周期性任务。
  • 缺点:
    不适合复杂调度场景。

6. 使用 Fork/Join 框架

Fork/Join
是 Java 7 引入的一种并行计算框架,适合将大任务分解成多个子任务并行处理。

示例代码

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

class SumTask extends RecursiveTask<Integer> {
    private final int start, end;

    public SumTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if (end - start <= 10) {
            int sum = 0;
            for (int i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            int mid = (start + end) / 2;
            SumTask leftTask = new SumTask(start, mid);
            SumTask rightTask = new SumTask(mid + 1, end);
            invokeAll(leftTask, rightTask);
            return leftTask.join() + rightTask.join();
        }
    }
}

public class ForkJoinExample {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        SumTask task = new SumTask(1, 100);
        System.out.println("总和:" + pool.invoke(task));
    }
}

场景解析

适合大量数据的并行处理,比如递归计算。

优缺点

  • 优点:
    提高多核 CPU 的利用率。
  • 缺点:
    不适合 I/O 密集型任务。

7. 使用 CompletableFuture

CompletableFuture
是 Java 8 提供的一种异步编程工具,支持链式调用,非常适合复杂任务的分解与组合。

示例代码

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            System.out.println("任务执行:" + Thread.currentThread().getName());
            return "任务结果";
        }).thenApply(result -> {
            System.out.println("处理结果:" + result);
            return "最终结果";
        }).thenAccept(System.out::println);
    }
}

场景解析

适用于异步任务链式调用,比如远程服务调用。

优缺点

  • 优点:
    功能强大,代码简洁。
  • 缺点:
    学习成本较高。

8. 使用 Guava 的 ListenableFuture

Guava 提供了
ListenableFuture
,对
Future
进行了增强,支持任务完成后的回调处理。

import com.google.common.util.concurrent.*;

import java.util.concurrent.Executors;

public class ListenableFutureExample {
    public static void main(String[] args) {
        ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(2));

        ListenableFuture<String> future = service.submit(() -> {
            Thread.sleep(1000);
            return "任务完成";
        });

        Futures.addCallback(future, new FutureCallback<String>() {
            @Override
            public void onSuccess(String result) {
                System.out.println("任务成功,结果:" + result);
            }

            @Override
            public void onFailure(Throwable t) {
                System.out.println("任务失败:" + t.getMessage());
            }
        }, service);

        service.shutdown();
    }
}

总结

以上就是 Java 中创建线程的 8 种方法,每一种方法都有其适用场景和优缺点。

下面给大家总结一下各自的优缺点:

方法 适用场景 优点 缺点
继承Thread类 简单任务 直观易懂 限制了类的继承
实现Runnable接口 大多数场景 灵活,不影响继承关系 无返回值
实现Callable接口 返回结果或抛异常的任务 支持返回值 需要配合
FutureTask
使用
线程池(ExecutorService) 高并发任务 高效管理线程 配置复杂
ScheduledExecutorService 周期性任务 易于实现定时调度 不适合复杂调度
Fork/Join框架 数据并行计算 提高多核利用率 不适合 I/O 密集型任务
CompletableFuture 异步任务链式调用 功能强大 学习曲线高
Guava的ListenableFuture 异步任务并带回调 回调机制强大,扩展性好 引入了第三方依赖

希望大家在实际开发中,能根据场景选择合适的方式。

比如:小任务用 Runnable,复杂计算用 Callable,高并发场景用线程池,而异步任务可以用 CompletableFuture 或 ListenableFuture等等。

通过这些方法的组合,可以让你的代码更加高效、优雅!

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。