📚 残梦三生

记录技术成长,分享学习心得

← 返回首页

Java和Go对比—多线程

分类:编程语言 | 日期:2023-12-13

1 Java中的线程池

1.1 简介

熟悉Java的人都知道,在Java多线程的运用中,线程池是一个非常好的工具,它可以避免在处理短时间任务时创建和销毁线程,起到充分利用内核、防止线程过分调度的作用,那么java都有那些线程池呢?

1.2 Java线程池

FixedThreadPool(固定线程池):该线程池包含固定数量的线程,适合处理数量已知且较小的任务。当所有线程都处于活动状态时,新的任务会被放入等待队列中。

CachedThreadPool(缓存线程池):创建一个可缓存线程池,线程池的线程数量不固定,可以根据任务的数量动态调整。如果线程池中有空闲线程,则会重复利用它们,否则会创建新的线程。适用于大量短期任务的场景。

SingleThreadExecutor(单线程池):该线程池只包含一个线程,用于按顺序执行任务。适合需要保证任务顺序执行的场景,比如单线程执行文件的读写操作。

ScheduledThreadPool(定时线程池):这个线程池可用于按延迟或定时周期执行任务。可以通过schedule方法设定任务的执行时间。

WorkStealingPool(工作窃取线程池):这线程池是Java 8新增的一种线程池。它是一种拥有多个工作队列的线程池,可以提高任务的并行度。每个线程都有自己的工作队列,当线程完成自己队列中的任务后,可以从其他线程的队列中窃取任务执行。

1.3 Java线程池的使用

上面一共列举了Java的Executors工具类所提供的5种线程池。但是由于它们在源源不断的任务加入到队列或者创建大量线程时,会发生内存耗尽的情况。所以在阿里规范中不建议直接使用这些自带的线程池,而是使用一些第三方库里面的线程池,如Apache Commons Pooling。

2 Java和Go的异同

从上面的内容可以看出,Java拥有许多种线程池,同时也拥有第三方库对Java线程池的支持。那么在Go语言中,是否需要线程池呢?首先我们先对比一下两种语言的差异。

2.1 相同点

支持并发编程:Java和Go都提供了原生支持并发编程的机制,使得开发者能够轻松地创建和管理多个并发执行的线程。

提供线程抽象:两者都提供了高级的线程抽象,如Java中的Thread类和Runnable接口,以及Go中的goroutine。

支持锁和同步:Java和Go都提供了锁和同步机制,如Java中的synchronized关键字和Lock接口,以及Go中的互斥锁(Mutex)和通道(Channel)等。

2.2 不同点

语言特性:Java是一种面向对象的语言,而Go是一种面向过程和函数式编程的语言。这导致在多线程编程时,两种语言的代码风格和实现方式有所不同。

并发模型:Java 中 CPU 资源分配对象是 Thread,Go 中 CPU 资源分配对象是 goroutine。Java Thread 与系统线程为一一对应关系,goroutine 是 Go 实现的用户级线程,与系统线程是 m:n 关系。

3 Go是否需要线程池

从Go的并发模型特点可以看出,Go鼓励使用goroutine和通道来实现并发和并行操作,以避免传统线程池的一些问题,如线程创建和销毁的开销、锁的竞争等。

在Go中,可以使用goroutine来启动轻量级的并发执行单元,而不是直接操作线程。goroutine的创建和销毁开销很小,因此可以创建大量的goroutine,并行执行任务。此外,通过使用通道进行同步和通信,可以确保数据的安全访问。

当然,如果必须使用线程池,可以通过一些第三方库来实现类似线程池的功能。一些常用的库包括golang.org/x/sync/semaphoregithub.com/Workiva/go-datastructures/workerpool等。

这些库通常允许你创建一组固定数量的goroutine,并将任务提交到池中进行执行。它们会自动管理goroutine的生命周期,避免过度创建和销毁。

4 代码实现

4.1 Java线程池实现

public class Test {
    static Integer j = 0;

    static synchronized void add() {
        j++;
    }
    static ExecutorService executorService = ExecutorBuilder.create()
            .setCorePoolSize(2)
            .setMaxPoolSize(2)
            .setWorkQueue(new LinkedBlockingQueue<>(100))
            .build();
    public static void main(String[] args) throws Exception {
        Future<?> future = executorService.submit(() -> {
            for (int i = 0; i < 10000; i++) {
                add();
            }
        });
        Future<?> future1 = executorService.submit(() -> {
            for (int i = 0; i < 10000; i++) {
                add();
            }
        });
        future.get();
        future1.get();
        System.out.println(j);
    }
}

4.2 Go多线程

根据Go语言对于线程池的态度,在此就是用常规的写法实现Go的多线程写法,不使用第三方库来实现多线程。

package main

import (
    "fmt"
    "sync"
)

var j int32 = 0
var mutex sync.Mutex

func add() {
    mutex.Lock()
    j++
    mutex.Unlock()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        for i := 0; i < 10000; i++ {
            add()
        }
        defer wg.Done()
    }()

    go func() {
        for i := 0; i < 10000; i++ {
            add()
        }
        defer wg.Done()
    }()
    wg.Wait()
    fmt.Println(j)
}

5 总结

Go语言本身不拥有线程池,因为它使用的是goroutine和通道来实现并发和并行。当然这并不是说它不支持线程池,在运用中,如果需要限制程序的并发量,也是可以通过第三方库来实现线程池运用的。