自定义Future+AQS实现异步转同步

引言

并发编程是 Java 编程中的一个复杂且重要的领域。在处理多线程环境下的数据同步和线程协作时,准确地控制线程间的协作成为了一个核心挑战。Java 提供了多种同步工具,但有时我们需要更细粒度的控制。这时,AbstractQueuedSynchronizer(AQS)就显得尤为重要。AQS 是构建锁和其他同步组件的强大框架。本文将介绍如何利用 AQS 实现一个自定义的同步机制,具体来说,是一个自定义的 Future 类。

使用AQS+Future而不是直接使用CompletableFuture ?

这个疑问在我第一次遇到AQS+Future的使用方法时也有疑问,因此这里顺带说一下这个,其实大多数场景下直接使用CompletableFuture就够用了。但是如果你的异步任务比较复杂,就需要使用AQS来进行管控了。下面给出两种方法的各自优势及选择依据,根据你的实际使用场景进行抉择。

使用 CompletableFuture 的优势

  1. 内置的高级功能CompletableFuture 提供了丰富的 API,用于处理异步编程的复杂场景,如组合多个异步操作、处理异常、转换和消费结果等。
  2. 链式调用CompletableFuture 支持函数式编程风格,允许你以链式调用的方式组织复杂的异步流程。
  3. 简化的异步编程CompletableFuture 使得异步编程更加直观和易于理解,尤其是在处理多个依赖的异步任务时。

使用 AQS 的优势

  1. 更细粒度的控制:AQS 提供了一种底层的同步机制,允许你构建出高度定制化的同步工具。对于一些特殊需求,这可能比 CompletableFuture 提供更多的控制能力。
  2. 性能优化:对于一些性能敏感的应用,使用 AQS 可以减少一些额外的开销,尤其是在你完全理解并能够优化同步机制时。
  3. 特定的同步语义:如果你需要实现一些 CompletableFuture 不支持的同步语义,AQS 可以提供一种解决方案。

选择依据

  • 如果你的应用场景涉及到复杂的异步逻辑,需要组合和转换多个异步操作的结果,那么 CompletableFuture 是一个很好的选择。
  • 如果你需要实现一些非标准的同步特性,或者你的应用对性能有极高的要求,那么使用 AQS 可能更合适。

AQS+Future

接下来就AQS+Future的方法进行介绍,至于CompletableFuture的使用可以看官方文档自行学习。对于Future想必大家都比较熟悉了,这里就不做过多介绍了,主要再简单回顾一下AQS的原理。

AQS 简介

AbstractQueuedSynchronizer,作为 Java 并发包中的一个核心类,是构建锁和其他同步组件的基础。它使用一个 int 成员变量来表示同步状态,并通过内置的 FIFO 队列来管理线程的阻塞和唤醒。AQS 的主要作用是作为构建锁和其他同步器的基础。例如,ReentrantLock、CountDownLatch 和 Semaphore 等都是基于 AQS 实现的。

核心概念
  1. 同步状态(Synchronization State):AQS 内部维护一个 volatile int 类型的变量来表示同步状态。子类可以通过提供的方法来操作这个状态。
  2. 等待队列(Wait Queue):如果线程获取同步状态失败,AQS 可以将线程加入到等待队列中。当同步状态释放时,可以按照队列中的顺序来唤醒线程。
关键方法
  • acquire(int arg)release(int arg):这些是最常用的方法,用于获取和释放同步状态。子类需要覆写 tryAcquire(int arg)tryRelease(int arg) 来定义这些方法的具体行为。
  • acquireShared(int arg)releaseShared(int arg):用于实现共享模式的同步器,例如 CountDownLatchSemaphore
自定义同步器

要实现一个基于 AQS 的同步器,通常需要:

  1. 决定同步器是在独占模式还是共享模式下工作。
  2. 根据需要覆写 tryAcquiretryReleasetryAcquireSharedtryReleaseShared 方法。
  3. 根据需要覆写 isHeldExclusively 方法(如果支持独占模式)。

示例:实现一个简单的互斥锁

下面是一个使用 AQS 实现的简单互斥锁的例子。这个锁是非重入的,即同一个线程尝试再次获取锁时会被阻塞。

class Mutex {
    // 自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        // 是否处于占用状态
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 当状态为0时获取锁
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 释放锁,将状态设置为0
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    }

    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}

使用 Mutex

class MutexDemo {
    private final Mutex mutex = new Mutex();

    public void safeAction() {
        mutex.lock();
        try {
            // 访问或修改共享资源
        } finally {
            mutex.unlock();
        }
    }
}

在这个例子中,Mutex 类包含一个 Sync 对象,SyncAbstractQueuedSynchronizer 的子类,用于管理同步状态。锁的获取和释放操作通过调用 Sync 对象的方法来实现。

实现自定义 Future 类

利用 AQS 实现一个自定义的 Future 类是 AQS 应用的一个典型场景。Future 通常用于异步计算场景,它提供了一种检查计算是否完成的机制,并能在计算完成后获取其结果。这里以RPC方法调用为例

示例实现

以下是一个简单的RPC远程调用类实现,使用 AQS 来同步远程方法的调用结果状态,确保在调用远程方法执行完成后同步返回结果:

其细节这里不再赘述,主要说明下具体逻辑:

(1)使用AQS实现Sync类来实现对计算方法执行结束状态及结果的管控

(2)将Sync作为静态内部类嵌入在CustomFuture方法中,结合Future方法来实现阻塞及结果获取

import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class CustomFuture<T> implements Future<T> {
    private final Sync sync = new Sync();
    private T result; // 存储异步操作的结果
    private Throwable throwable; // 存储异步操作的异常
    // 定义AQS自定义类,用于对异步计算的状态、结果进行管控
    private final class Sync extends AbstractQueuedSynchronizer {
        // 是否完成
        protected boolean isDone() {
            return getState() != 0;
        }
        // 完成后更改状态值为1
        protected boolean tryComplete() {
            return compareAndSetState(0, 1);
        }
        // 阻塞循环获取执行结果
        protected void await() throws InterruptedException {
            // 注意区分与acquire()方法的区别,这里在线程中断后会抛出InterruptedException
            acquireInterruptibly(0);
        }
        // 释放共享状态,唤醒线程
        protected void release() {
            releaseShared(0);
        }
    }
    // 正常执行完毕
    public void complete(T result) {
        this.result = result;
        sync.release();
    }
    // 异常结束
    public void completeExceptionally(Throwable throwable) {
        this.throwable = throwable;
        sync.release();
    }

    @Override
    public boolean isDone() {
        return sync.isDone();
    }

    @Override
    public T get() throws InterruptedException {
        sync.await();
        if (throwable != null) {
            throw new RuntimeException(throwable);
        }
        return result;
    }

    // 省略其他方法
}


使用方法:

通过子线程异步调用计算方法,然后主线程同步阻塞获取计算结果。(这里的异步计算例子并不复杂,使用CompletableFuture也可以实现同样的效果)

CustomFuture<String> future = new CustomFuture<>();
// 异步执行某些操作
new Thread(() -> {
    try {
        // 模拟耗时操作
        Thread.sleep(1000);
        future.complete("Success");
    } catch (InterruptedException e) {
        future.completeExceptionally(e);
    }
}).start();

// 在其他地方等待结果
try {
    String result = future.get(); // 阻塞直到异步操作完成
    System.out.println("异步操作的结果: " + result);
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (RuntimeException e) {
    System.err.println("异步操作失败: " + e.getCause());
}

应用场景

这个自定义的 Future 类可以用于任何需要异步执行任务并等待结果的场景。例如,在 Web 应用中进行远程服务调用时,可以使用这种 Future 来等待响应,同时不阻塞主线程的执行。

这里给出一个RPC框架中使用AQS+Future的简单模板示例,感兴趣的可以研究一下,其实在许多成熟的框架中有很多地方都通过这种方式来实现异步结果的获取,希望大家能够掌握这种方法。

public class RpcFuture extends CompletableFuture<Object> {

    private static final Logger logger = LoggerFactory.getLogger(RpcFuture.class);

    private Sync sync;

    private RpcProtocol<RpcRequest> requestRpcProtocol;

    private RpcProtocol<RpcResponse> responseRpcProtocol;

    private long startTime;

    private long responseTimeThreshold = 5000;

    public RpcFuture(RpcProtocol<RpcRequest> requestRpcProtocol) {
        this.sync = new Sync();
        this.requestRpcProtocol = requestRpcProtocol;
        this.startTime = System.currentTimeMillis();
    }

    @Override
    public boolean isDone() {
        return sync.isDone();
    }
	// 阻塞获取执行结果
    @Override
    public Object get() throws InterruptedException, ExecutionException {
        sync.acquire(-1);
        if (this.responseRpcProtocol != null) {
            return responseRpcProtocol.getBody().getResult();
        } else {
            return null;
        }
    }
	// 定义超时获取结果
    @Override
    public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        boolean success = sync.tryAcquireNanos(-1, unit.toNanos(timeout));
        if (success) {
            if (this.responseRpcProtocol != null) {
                return this.responseRpcProtocol.getBody().getResult();
            } else {
                return null;
            }
        } else {
            throw new RuntimeException("Timeout exception. Request id:" +
                    this.requestRpcProtocol.getRpcHeader().getRequestId()
                    + ". Request class name:" +
                    this.requestRpcProtocol.getBody().getClassName() +
                    ". Request method:" +
                    this.requestRpcProtocol.getBody().getMethodName());
        }
    }
	// 取消
    @Override
    public boolean isCancelled() {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        throw new UnsupportedOperationException();
    }
    
    public void done(RpcProtocol<RpcResponse> responseRpcProtocol) {
        this.responseRpcProtocol = responseRpcProtocol;
        sync.release(1);
        // Threshold
        long responseTime = System.currentTimeMillis() - startTime;
        if (responseTime > this.responseTimeThreshold) {
            logger.warn("Service response time is too slow. RequestId = " + this.requestRpcProtocol.getRpcHeader().getRequestId()
             + ". responseTime = " + responseTime + " ms");
        }
    }

    static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1L;

        // future status
        private final int done = 1;
        private final int pending = 0;
        // 当前状态已完成则返回true,表示远程方法执行完毕,并返回结果
        // 这个方法会被AQS的acquire调用,会循环调用,直到返回true,即获取结果
        protected boolean tryAcquire(int acquires) {
            return getState() == done;
        }
        // 远程方法执行完成后调用,将状态设置为已完成
        protected boolean tryRelease(int releases) {
            if (getState() == pending) {
                if (compareAndSetState(pending, done)) {
                    return true;
                }
            }
            return false;
        }
        // 判断任务是否已经完成
        public boolean isDone() {
            getState();
            return getState() == done;
        }
    }
}

结论

AQS 是 Java 并发包中的一个强大工具,它不仅是许多同步组件的基础,而且还可以用来构建符合特定需求的自定义同步工具。通过继承 AQS 并实现必要的方法,可以创建如自定义的 Future 类这样的有用的同步机制,从而优雅地处理多线程环境下的复杂问题。注意在使用 AQS 时,需要深入理解其工作原理和 Java 并发编程的基本概念,以确保实现的正确性和效率。

你可能感兴趣的:(java,java)