在 Java 的并发编程世界中,阻塞方法是我们必须深入理解的核心概念。这些方法具有特殊的性质,它们会阻塞当前线程的执行,直到其操作完全完成。这意味着,线程会停下来等待,直到满足特定条件使其任务得以继续。正是这种天然的阻塞特性,赋予了它们“阻塞方法”的名称。
让我们看一个典型的例子:InputStream 的 read() 方法会一直阻塞,直到输入流中的数据被完全读取为止。除了这个,Java 中还有许多我们常见的阻塞方法:
- InvokeAndWait(): 它会阻塞当前线程,直到事件分发线程执行完特定代码。
- InputStream.read(): 它会持续阻塞,直到有输入数据可读、抛出异常或检测到流结束。
- ServerSocket.accept(): 监听传入的 Java socket 连接,在此期间一直阻塞,直到建立连接。
- CountDownLatch.await(): 导致当前线程等待,直到锁存器的计数减为零(除非线程被中断)。
虽然这些方法非常有用,但我们也必须认识到使用阻塞方法带来的几个潜在劣势:
- 阻塞技术会对系统的可扩展性构成重大挑战。经典的阻塞解决方案通常采用多线程来服务多个客户端,以此作为缓解阻塞的一种手段。
- 系统设计在这里显得尤为关键。因为即使是多线程系统,其能力也存在上限;设计不当的系统由于 JVM 线程数量的限制,可能仅能支撑几百或几千个线程。
实现示例:
让我们通过下面的例子来实际体验一下。在执行完第一个打印语句后,程序会在第二个打印语句处被“阻塞”。这是因为 read() 方法在等待输入,直到你在控制台输入一些字符并按下回车键,程序才会继续运行。
示例 1:
Java
CODEBLOCK_69d45f3c
输出
GFG
Geeks for Geeks
示例 2:
在这个例子中,我们将看到 INLINECODE9333c6d6 是如何工作的。当一个线程需要等待其他几个线程完成工作后才能开始时,这个方法非常有用。其中的 INLINECODEcbe2bcb1 方法负责减少计数,而 await() 方法则会阻塞线程,直到计数归零。
Java
“
// Java 程序演示阻塞方法
// 导入所有输入输出类
import java.io.*;
// 导入并发 CountDownLatch 类
// 来自 java.util 包
import java.util.concurrent.CountDownLatch;
// 类
class Main {
// Main 驱动方法
public static void main(String args[])
throws InterruptedException
{
// 创建一个任务,让它等待
// 四个线程完成后才开始
CountDownLatch latch = new CountDownLatch(4);
// 创建 Person 类型的线程
// 自定义参数输入
Person p1 = new Person(1500, latch, "PERSON-1");
Person p2 = new Person(2500, latch, "PERSON-2");
Person p3 = new Person(3500, latch, "PERSON-3");
Person p4 = new Person(4500, latch, "PERSON-4");
Person p5 = new Person(5500, latch, "PERSON-5");
// 启动线程
// 使用 start() 方法
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
// 等待四个线程
// 使用 latch.await() 方法
latch.await();
// 主线程已开始
System.out.println(Thread.currentThread().getName()
+ " has finished his work");
}
}
// 类 2
// 辅助类,继承 Thread 类
// 代表主线程等待的其他线程
class Person extends Thread {
// 此类的成员变量
private int delay;
private CountDownLatch latch;
// 此类的方法
public Person(int delay, CountDownLatch latch,
String name)
{
// super 指代父类
super(name);
// this 关键字指代当前对象本身
this.delay = delay;
this.latch = latch;
}
@Override public void run()
{
// Try 块检查异常
try {
Thread.sleep(delay);
latch.countDown();
// 打印当前线程名称
// 使用 getName() 方法
// 表示已完成工作
System.out.println(
Thread.currentThread().getName()
+ " has finished his work");
}
// Catch 块处理异常
catch (InterruptedException e) {
// 打印异常发生的行号