【Java&Go并发编程系列】2.阻塞队列与通道——BlockingQueue VS channel(下篇)

本文对比分析了Java中的LinkedBlockingQueue与Go语言的通道在并发编程中的应用,包括基本操作、超时处理及容量差异。通过具体代码示例,详细解释了两种语言在阻塞队列和通道上的相似之处与关键差别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说明:Java & Go 并发编程序列的文章,根据每篇文章的主题或细分标题,分别演示 Java 和 Go 语言当中的相关实现。更多该系列文章请查看:Java & Go 并发编程系列

上一篇从以下维度介绍了 Java 中的阻塞队列和 Go 语言通道。

零容量有限容量
Gounbuffered channelbuffered channel
JavaSynchronousQueueLinkedBlockingQueue

本篇将进一步介绍有限容量的 buffered channel 和 LinkedBlockingQueue 的基本操作。然后再单独说明零容量的工具与有限容量在操作上的主要差异。

从尾部添加 VS 发送
  • 「Java」往 LinkedBlockingDeque 尾部添加一个元素,如果队列已满,则阻塞
// 创建一个容量为1的阻塞队列
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1);
queue.put(1);// 添加成功
queue.put(2);// 阻塞在这里,直到队列有可用的空间
  • 「Go」往通道发送一个元素值,如果通道已满,则阻塞
// 创建容量为1,元素类型为int的通道
ch := make(chan int, 1)
ch <- 1 // 发送成功
ch <- 2 // 阻塞在这里,直到通道有可用的空间

如果不希望一直阻塞,我们可以给以上操作设置一个超时时间

  • 「Java」使用 offer 操作(注意区别前面的 put),并设置超时时间
 // 创建一个容量为1的阻塞队列
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1);
System.out.printf("是否添加成功:%b\n", queue.offer(1));// 是否添加成功:true
System.out.printf("是否添加成功:%b\n", queue.offer(2, 1, TimeUnit.SECONDS));// 1s后返回false
  • 「Go」结合 select 语句实现超时不执行操作
// 创建容量为1,元素类型为int的通道
ch := make(chan int, 1)
ch <- 1 // 发送成功
// select 语句是专门针对通道而设计的语句
// 通常包含 case 表达式和一个可选的 defalt 分支
// case 表达式中只能包含操作通道的表达式,如发送或接收表达式
// 如果没有 default 分支,则会阻塞到其中的一个 case 语句被选中
// 这里是1s后第二个分支被选中(从接收通道接收到一个值)
select {
case ch <- 2: // 该分支不会被选中执行
	fmt.Printf("写入成功\n")
case <-time.After(time.Second * 1):
	fmt.Printf("1s后超时退出\n")
}
从头部移除并返回 VS 接收
  • 「Java」从 LinkedBlockingDeque 头部移除并返回一个元素,如果队列为空,则阻塞
// 创建一个容量为1的阻塞队列
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1);

// 新起一个线程,500ms 之后往队列尾部添加元素
Thread thread = new Thread(() -> {
    try {
        TimeUnit.MILLISECONDS.sleep(500);
        queue.put(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
thread.start();
// 阻塞直到获取到元素,如果获取不到则永远阻塞在这里
System.out.printf("阻塞片刻之后返回结果: %d\n", queue.take()); // 阻塞片刻之后返回结果:1
  • 「Go」从通道接收元素值,如果通道为空,则阻塞
// 创建容量为1,元素类型为int的通道
ch:= make(chan int, 1)

// 从另一个 Goroutine 500ms 后往通道发送一个元素值
go func() {
	time.Sleep(time.Millisecond * 500)
	ch <- 1
}()
// 阻塞直到接收到元素值
fmt.Printf("阻塞片刻之后返回结果: %d\n", <-ch) // 阻塞片刻之后返回结果: 1

同样我们可以给以上操作设置超时时间

  • 「Java」使用 poll 操作(注意区别前面的 take),并设置超时时间
// 创建一个容量为1的阻塞队列
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1);

// 新起一个线程,500ms 之后往队列尾部添加元素
Thread thread = new Thread(() -> {
    try {
        TimeUnit.MILLISECONDS.sleep(500);
        queue.put(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
thread.start();
// 如果超时获取不到则返回 null
System.out.printf("设置1s超时返回结果: %d\n", queue.poll(1, TimeUnit.SECONDS));// 设置1s超时返回结果: 1
  • 「Go」结合 select 语句实现超时不执行操作
// 创建容量为1,元素类型为int的通道
ch := make(chan int, 1)

go func() {
	// 从另一个 Goroutine 500ms 后往通道发送一个元素值
	time.Sleep(time.Millisecond * 500)
	ch <- 1
}()

select {
// 由于该 case 先从通道接收到值所以被选中
case number := <-ch:
	fmt.Printf("结果: %d\n", number) // 结果: 1
case <-time.After(time.Second * 1):
	fmt.Printf("1s后超时退出\n")
}
零容量的工具与有限容量在操作上的主要差异

Java 中的 SynchonousQueue 的以下操作与 LinkedBlockingQueue 保持一致:

  • put()
  • take()
  • offer(E e, long timeout, TimeUnit unit)
  • poll(long timeout, TimeUnit unit)

主要差异在于 SynchonousQueue 内部没有容量,所以获取剩余容量的时候总是返回0。

/**
 * Always returns zero.
 * A {@code SynchronousQueue} has no internal capacity.
 *
 * @return zero
 */
public int remainingCapacity() {
    return 0;
}

另外元素的传递也是直接由发送方直接传递给接收方,不像 LinkedBlockingQueue 一定会经过内部的队列。

同样的,对于 Go 语言的非缓冲通道,其容量也总是为0

unbufChan := make(chan int)           // 创建一个非缓冲通道
fmt.Printf("容量为%d\n", cap(unbufChan)) // 容量为0
fmt.Printf("长度为%d\n", len(unbufChan)) // 长度为0

bufChan := make(chan int, 8)        // 创建一个缓冲通道
fmt.Printf("容量为%d\n", cap(bufChan)) // 容量为8
fmt.Printf("长度为%d\n", len(bufChan)) // 长度为0
bufChan <- 1
fmt.Printf("容量为%d\n", cap(bufChan)) // 容量为8
fmt.Printf("长度为%d\n", len(bufChan)) // 长度为1

关于队列和通道的容量和获取方式总结如下:

其中队列(或通道)的长度代表它当前包含的元素值的个数。当队列(或通道)已满时,其长度与容量相同。

容量长度剩余容量
SynchonousQueue000
LinkedBlockingQueue构造函数指定的capacitysize()remainingCapacity()
unbuffered channel000
buffered channelcap(ch)len(ch)cap(ch) - len(ch)

ch 表示通道。

更多该系列文章请查看:Java & Go 并发编程系列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值