阻塞和非阻塞的区别?

在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中的阻塞示例
  1. 阻塞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); // 阻塞,无数据时线程暂停
    
  2. 线程同步机制

    • synchronized块:线程获取不到锁时,会进入阻塞状态。
    • Object.wait():线程会释放锁并进入阻塞状态,直到被notify()唤醒。
    synchronized (lock) {
        while (!condition) {
            lock.wait(); // 线程阻塞,释放锁
        }
    }
    
  3. 线程休眠
    Thread.sleep(1000)会让线程阻塞1秒,期间不占用CPU。

三、非阻塞:线程"主动询问"模式

非阻塞操作中,即使资源未就绪,线程也不会挂起,而是立即返回一个"未完成"的状态(如-1null),线程可以继续执行其他任务,之后通过轮询再次尝试操作。

这就像你去食堂打饭,若窗口没备好你的餐,你不会站着等,而是先去买瓶水,过一会儿再回来问问是否做好了。

Java中的非阻塞示例
  1. 非阻塞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) {
        // 无数据,可先处理其他任务
    }
    
  2. NIO的Selector(多路复用)
    非阻塞模式通常配合Selector使用,避免无效轮询:

    Selector selector = Selector.open();
    // 注册非阻塞通道到Selector,关注可读事件
    channel.register(selector, SelectionKey.OP_READ);
    
    // 轮询就绪的通道(非阻塞或超时阻塞)
    while (true) {
        int readyChannels = selector.selectNow(); // 非阻塞,立即返回就绪通道数
        if (readyChannels == 0) {
            // 无就绪事件,处理其他任务
            continue;
        }
        // 处理就绪的通道
    }
    
  3. 原子操作类(非阻塞同步)
    java.util.concurrent.atomic包下的类(如AtomicInteger)使用CAS(Compare-And-Swap)实现非阻塞同步:

    AtomicInteger count = new AtomicInteger(0);
    // 非阻塞更新:若当前值为0,则更新为1,失败则立即返回false
    boolean updated = count.compareAndSet(0, 1);
    

四、关键区别总结

  1. 线程状态

    • 阻塞:线程会进入BLOCKEDWAITING状态,暂停执行,不占用CPU。
    • 非阻塞:线程始终处于RUNNABLE状态,持续执行(可能执行其他任务)。
  2. 资源效率

    • 阻塞:适合长等待场景(如I/O),不浪费CPU,但线程切换有开销。
    • 非阻塞:适合短等待场景,避免线程切换,但轮询可能消耗额外CPU。
  3. 编程复杂度

    • 阻塞:逻辑简单,直接等待结果即可(如BIO的read())。
    • 非阻塞:需手动处理"未就绪"状态,通常需要轮询或事件通知(如NIO的Selector)。
  4. 适用场景

    • 阻塞:连接数少、操作耗时较长的场景(如文件读写、数据库查询)。
    • 非阻塞:高并发、操作耗时短的场景(如网络服务器、高频交易系统)。

五、Java中阻塞与非阻塞的典型应用对比

操作类型阻塞实现非阻塞实现
网络I/OSocketServerSocket(BIO)SocketChannelSelector(NIO)
文件I/OFileInputStreamFileOutputStream无原生非阻塞文件I/O(需依赖NIO.2的异步API)
线程同步synchronizedObject.wait()Atomic*ConcurrentHashMap
连接处理一个连接一个线程单线程处理多个连接(多路复用)

总结

阻塞和非阻塞的核心区别在于线程等待时是否被挂起:阻塞通过"暂停等待"减少CPU消耗,非阻塞通过"主动询问"提高并发效率。

在Java开发中,阻塞模式实现简单但并发能力有限(如BIO),非阻塞模式(如NIO)通过复杂的轮询和事件机制支持高并发,是高性能网络编程的首选(如Netty框架)。实际选择时需权衡场景复杂度、资源消耗和并发需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值