在Java中,阻塞(Blocking)和非阻塞(Non-blocking)是描述线程在等待资源或操作完成时的两种状态,核心区别在于线程在等待期间是否会被挂起(暂停执行)。这一特性直接影响程序的并发能力和资源利用率,尤其在I/O操作中表现显著。
一、核心定义与本质区别
特性 | 阻塞(Blocking) | 非阻塞(Non-blocking) |
---|---|---|
等待状态 | 线程会被挂起(进入阻塞状态),暂停执行 | 线程不会被挂起,始终保持运行状态 |
资源占用 | 阻塞时不消耗CPU资源(线程休眠) | 可能消耗CPU资源(需轮询检查状态) |
操作结果 | 操作完成后线程被唤醒,直接获取结果 | 无论操作是否完成,立即返回状态(成功/未就绪) |
典型场景 | BIO(阻塞I/O)、Object.wait() 、Thread.sleep() | NIO(非阻塞I/O)、ConcurrentHashMap 的非阻塞操作 |
二、阻塞:线程"暂停等待"模式
当线程执行阻塞操作时,若所需资源未就绪(如I/O数据未到达、锁被占用),线程会被操作系统挂起,暂时让出CPU资源,直到资源就绪后再被唤醒继续执行。
这就像你去食堂打饭,若窗口还没备好你的餐,你会站在原地等待(不做其他事),直到饭菜做好。
Java中的阻塞示例
-
阻塞I/O(BIO)操作:
// 服务器端accept()阻塞:等待客户端连接时,线程挂起 ServerSocket serverSocket = new ServerSocket(8080); Socket socket = serverSocket.accept(); // 阻塞,无连接时线程暂停 // 输入流read()阻塞:无数据时线程挂起 InputStream in = socket.getInputStream(); byte[] buffer = new byte[1024]; int len = in.read(buffer); // 阻塞,无数据时线程暂停
-
线程同步机制:
synchronized
块:线程获取不到锁时,会进入阻塞状态。Object.wait()
:线程会释放锁并进入阻塞状态,直到被notify()
唤醒。
synchronized (lock) { while (!condition) { lock.wait(); // 线程阻塞,释放锁 } }
-
线程休眠:
Thread.sleep(1000)
会让线程阻塞1秒,期间不占用CPU。
三、非阻塞:线程"主动询问"模式
非阻塞操作中,即使资源未就绪,线程也不会挂起,而是立即返回一个"未完成"的状态(如-1
或null
),线程可以继续执行其他任务,之后通过轮询再次尝试操作。
这就像你去食堂打饭,若窗口没备好你的餐,你不会站着等,而是先去买瓶水,过一会儿再回来问问是否做好了。
Java中的非阻塞示例
-
非阻塞I/O(NIO)操作:
通过SocketChannel
的非阻塞模式,read()
和write()
会立即返回:// 开启非阻塞模式 SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); // 连接操作:非阻塞,立即返回(可能尚未完成连接) channel.connect(new InetSocketAddress("example.com", 80)); ByteBuffer buffer = ByteBuffer.allocate(1024); // 读取操作:非阻塞,无数据时返回0,不会挂起线程 int bytesRead = channel.read(buffer); if (bytesRead == 0) { // 无数据,可先处理其他任务 }
-
NIO的Selector(多路复用):
非阻塞模式通常配合Selector
使用,避免无效轮询:Selector selector = Selector.open(); // 注册非阻塞通道到Selector,关注可读事件 channel.register(selector, SelectionKey.OP_READ); // 轮询就绪的通道(非阻塞或超时阻塞) while (true) { int readyChannels = selector.selectNow(); // 非阻塞,立即返回就绪通道数 if (readyChannels == 0) { // 无就绪事件,处理其他任务 continue; } // 处理就绪的通道 }
-
原子操作类(非阻塞同步):
java.util.concurrent.atomic
包下的类(如AtomicInteger
)使用CAS(Compare-And-Swap)实现非阻塞同步:AtomicInteger count = new AtomicInteger(0); // 非阻塞更新:若当前值为0,则更新为1,失败则立即返回false boolean updated = count.compareAndSet(0, 1);
四、关键区别总结
-
线程状态:
- 阻塞:线程会进入
BLOCKED
或WAITING
状态,暂停执行,不占用CPU。 - 非阻塞:线程始终处于
RUNNABLE
状态,持续执行(可能执行其他任务)。
- 阻塞:线程会进入
-
资源效率:
- 阻塞:适合长等待场景(如I/O),不浪费CPU,但线程切换有开销。
- 非阻塞:适合短等待场景,避免线程切换,但轮询可能消耗额外CPU。
-
编程复杂度:
- 阻塞:逻辑简单,直接等待结果即可(如BIO的
read()
)。 - 非阻塞:需手动处理"未就绪"状态,通常需要轮询或事件通知(如NIO的
Selector
)。
- 阻塞:逻辑简单,直接等待结果即可(如BIO的
-
适用场景:
- 阻塞:连接数少、操作耗时较长的场景(如文件读写、数据库查询)。
- 非阻塞:高并发、操作耗时短的场景(如网络服务器、高频交易系统)。
五、Java中阻塞与非阻塞的典型应用对比
操作类型 | 阻塞实现 | 非阻塞实现 |
---|---|---|
网络I/O | Socket 、ServerSocket (BIO) | SocketChannel 、Selector (NIO) |
文件I/O | FileInputStream 、FileOutputStream | 无原生非阻塞文件I/O(需依赖NIO.2的异步API) |
线程同步 | synchronized 、Object.wait() | Atomic* 、ConcurrentHashMap |
连接处理 | 一个连接一个线程 | 单线程处理多个连接(多路复用) |
总结
阻塞和非阻塞的核心区别在于线程等待时是否被挂起:阻塞通过"暂停等待"减少CPU消耗,非阻塞通过"主动询问"提高并发效率。
在Java开发中,阻塞模式实现简单但并发能力有限(如BIO),非阻塞模式(如NIO)通过复杂的轮询和事件机制支持高并发,是高性能网络编程的首选(如Netty框架)。实际选择时需权衡场景复杂度、资源消耗和并发需求。