Java和Go对比—多线程
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/semaphore、github.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和通道来实现并发和并行。当然这并不是说它不支持线程池,在运用中,如果需要限制程序的并发量,也是可以通过第三方库来实现线程池运用的。