Netty系列-3 ChannelFuture和ChannelPromise介绍

news2025/1/11 6:09:28

背景

Netty源码中大量使用了Future和Promise,学习ChannelFuture和ChannelFuture有助于理解Netty的设计思路。
本文的重点内容在于梳理清楚这些类的关系以及结合源码实现介绍这些类的作用,其中核心逻辑在于DefaultPromise和DefaultChannelPromise,Netty中Future和Promoise相关的类和继承关系如下所示:
在这里插入图片描述
图中父类中有两个Future接口,一个是JUC定义的Future(后续用juc.Future表示),一个是Netty继承JUC.Future定义的Future.

1.juc.Future和Future

juc.Future源码如下所示:

public interface Future<V> {
    // 取消任务
    boolean cancel(boolean mayInterruptIfRunning);
    // 是否已被取消
    boolean isCancelled();
    // 任务是否已执行完成
    boolean isDone();

    // 阻塞式获取结果信息
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

在FutureTask源码分析中对juc.Future的接口和作用已进行过较为详细地介绍,这里不再赘述。
Netty中Future接口定义如下:

public interface Future<V> extends java.util.concurrent.Future<V> {
    boolean isSuccess();
    boolean isCancellable();
    Throwable cause();

    Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
    Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
    Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
    Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);

    Future<V> sync() throws InterruptedException;
    Future<V> syncUninterruptibly();
    Future<V> await() throws InterruptedException;
    Future<V> awaitUninterruptibly();
    boolean await(long timeout, TimeUnit unit) throws InterruptedException;
    boolean await(long timeoutMillis) throws InterruptedException;
    boolean awaitUninterruptibly(long timeout, TimeUnit unit);
    boolean awaitUninterruptibly(long timeoutMillis);

    V getNow();
}

从四个角度对juc.Future进行了增强:
[1] 状态相关
相对于juc.Future判断是否已完成isDone()、是否已取消isCancelled(),更细致地定义了是否已成功isSuccess(),且定义了用于保存任务失败场景的异常对象cause()
[2] 引入了监听器的概念并提供了添加和删除监听器接口,以支持异步回调;
[3] 引入了sync和await阻塞等待任务,并分别为超时和响应中断重载了方法;
[4] 获取方式引入了非阻塞的getNow()方法获取执行结果。

2.AbstractFuture

public abstract class AbstractFuture<V> implements Future<V> {
    @Override
    public V get() throws InterruptedException, ExecutionException {
        // 阻塞等待
        await();

        // 能执行到这里,说明阻塞已被唤醒,任务已执行完成或阻塞等待被中断
        Throwable cause = cause();
        if (cause == null) {
            return getNow();
        }
        if (cause instanceof CancellationException) {
            throw (CancellationException) cause;
        }
        throw new ExecutionException(cause);
    }

    @Override
    public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        // 等待timeout后自动苏醒,不需要被动唤醒
        if (await(timeout, unit)) {
            Throwable cause = cause();
            if (cause == null) {
                return getNow();
            }
            if (cause instanceof CancellationException) {
                throw (CancellationException) cause;
            }
            throw new ExecutionException(cause);
        }
        throw new TimeoutException();
    }
}

AbstractFuture内容较为简单,仅实现了两个阻塞的get方法。核心是借助await和await(timeout)实现阻塞,等待任务执行完成或被中断,然后调用getNow()非阻塞式地获取执行结果。如果执行成功,通过getNow()返回执行结果;如果执行失败,cause()返回异常类,get方法会抛出异常。

3.Promise和DefaultPromise

Promise作为Future的子类,其接口定义如下:

public interface Promise<V> extends Future<V> {
    // Promise相对Future拥有可以操作结果值的能力
	Promise<V> setSuccess(V result);
    boolean trySuccess(V result);
    Promise<V> setFailure(Throwable cause);
    boolean tryFailure(Throwable cause);
    boolean setUncancellable();
	
	// 重载await/sync和listener的返回值为Promise
    @Override Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
    @Override Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
    @Override Promise<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
    @Override Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
    @Override Promise<V> await() throws InterruptedException;
    @Override Promise<V> awaitUninterruptibly();
    @Override Promise<V> sync() throws InterruptedException;
    @Override Promise<V> syncUninterruptibly();
}

相对于Future的只读提供了写(操作结果)的能力,如setSuccess/trySuccess, setFailure/tryFailure等; 另外,将Future中await和sync和监听器接口的返回值重载为Promise类型。

3.1 DefaultPromise属性

DefaultPromise作为Promise的实现类,是一个完整的类,可以直接使用。先看一下属性:

public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
	// 存在执行结果或异常对象(执行失败)
	private volatile Object result;
    
    // Promise内部用于执行任务的线程池对象,netty框架中,使用NioEventLoop线程
	private final EventExecutor executor;
	
    // 监听器列表容器
    private Object listeners;
    
    // 因获取结果阻塞等待的线程数量
	private short waiters;
    
    //...
}
[1] result

用于存放执行结果,初始值为空。当任务正常执行时被设置执行结果,当任务执行异常时,被设置异常对象。
Netty引入原子对象包装result, 用于原子性地读取和设置result的值:

private static final AtomicReferenceFieldUpdater<DefaultPromise, Object> RESULT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(DefaultPromise.class, Object.class, "result");

// --同时引入几个常量对象--
private static final Object SUCCESS = new Object();
// UNCANCELLABLE用于表示设置Promise不可被取消
private static final Object UNCANCELLABLE = new Object();
// CANCELLATION_CAUSE_HOLDER用于设置异常结果
private static final CauseHolder CANCELLATION_CAUSE_HOLDER = new CauseHolder(ThrowableUtil.unknownStackTrace( new CancellationException(), DefaultPromise.class, "cancel(...)"));

此时,RESULT_UPDATER存在以下几种用法:

// 将result设置为不可取消状态
RESULT_UPDATER.compareAndSet(this, null, UNCANCELLABLE)

// 将result设置为objResult(只能从null或UNCANCELLABLE的初始状态)
RESULT_UPDATER.compareAndSet(this, null, objResult)
RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)

// 将result设置为异常状态
RESULT_UPDATER.compareAndSet(this, null, CANCELLATION_CAUSE_HOLDER)
RESULT_UPDATER.compareAndSet(this, CANCELLATION_CAUSE_HOLDER, new CauseHolder(ce));

Promise任务的取消:
当任务执行完成后,不可取消任务;也可以在任务执行前,将任务标识为禁止取消:

public boolean cancel(boolean mayInterruptIfRunning) {
    // 只有初始状态才可以取消任务
	if (RESULT_UPDATER.compareAndSet(this, null, CANCELLATION_CAUSE_HOLDER)) {
		if (checkNotifyWaiters()) {
			notifyListeners();
		}
		return true;
	}
	return false;
}

DefaultPromise提供了如下方法根据result的值返回Promise的状态:

// (1)是否成功,result被设置了正常值
@Override
public boolean isSuccess() {
    Object result = this.result;
    return result != null && result != UNCANCELLABLE && !(result instanceof CauseHolder);
}

// (2)是否已执行完成,被设置了正常值或者异常对象(执行失败也是执行完成)
@Override
public boolean isDone() {
    return isDone0(result);
}
private static boolean isDone0(Object result) {
    return result != null && result != UNCANCELLABLE;
}

// (3)只有初始状态,才可以被取消
@Override
public boolean isCancellable() {
    return result == null;
}

// (4)是否被取消了
@Override
public boolean isCancelled() {
    return isCancelled0(result);
}
private static boolean isCancelled0(Object result) {
    return result instanceof CauseHolder && ((CauseHolder) result).cause instanceof CancellationException;
}
[2] waiters

用于计数阻塞在获取该Promise/Future对象执行结果的线程数,这些线程调用Object的wait方法而处于等待队列中。当Promise设置为执行成功或者执行失败时,将会唤醒这些线程。因此,DefaultPromise源码中会看到synchronized和wait()/notifyAll()的使用。
线程的状态如等待和阻塞、同步队列等已在"线程的状态"经过系统和全面地介绍,这里不再赘述。

[3] listeners

监听器是一个列表对象,与该属性对应的方法有添加、删除、调用监听器。向列表中添加和删除元素的逻辑较为简单,这里重点看一下对监听器的调用,当任务执行完成时会触发notifyListeners方法:

private void notifyListeners() {
    EventExecutor executor = executor();
    if (executor.inEventLoop()) {
        final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
        final int stackDepth = threadLocals.futureListenerStackDepth();
        if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
            threadLocals.setFutureListenerStackDepth(stackDepth + 1);
            try {
                notifyListenersNow();
            } finally {
                threadLocals.setFutureListenerStackDepth(stackDepth);
            }
            return;
        }
    }

    safeExecute(executor, new Runnable() {
        @Override
        public void run() {
            notifyListenersNow();
        }
    });
}

notifyListeners方法会将调用监听器的方法提交给executor线程池对象进行(netty框架中对应NioEventLoopGroup).
继续跟进notifyListenersNow()方法:

private void notifyListenersNow() {
	Object listeners;
	synchronized (this) {
		if (notifyingListeners || this.listeners == null) {
			return;
		}
		notifyingListeners = true;
		listeners = this.listeners;
		this.listeners = null;
	}
	for (;;) {
		if (listeners instanceof DefaultFutureListeners) {
			notifyListeners0((DefaultFutureListeners) listeners);
		} else {
			notifyListener0(this, (GenericFutureListener<?>) listeners);
		}
		synchronized (this) {
			if (this.listeners == null) {
				notifyingListeners = false;
				return;
			}
			listeners = this.listeners;
			this.listeners = null;
		}
	}
}

逻辑较为简单,遍历listeners并调用notifyListener0方法:

private static void notifyListener0(Future future, GenericFutureListener l) {
    try {
        l.operationComplete(future);
    } catch (Throwable t) {
        if (logger.isWarnEnabled()) {
            logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t);
        }
    }
}

在try-catch绝对保护下调用监听器的operationComplete方法。

3.2 DefaultPromise方法

[1] await方法

DefaultPromise为不同场景提供了不同的await方法,其中:await()和await(timeout)的区别仅在于是否持续休眠或者仅休眠一段时间;类似wait()之于wait(timeout)。await与awaitUninterruptibly的区别在于是否响应中断,前者遇到中断时会抛出异常,后者会忽略中断信号。以await为代表介绍await系列方法的实现:isDone()

public Promise<V> await() throws InterruptedException {
    // 任务已完成,直接返回
    if (isDone()) {
        return this;
    }

    // 响应中断,如果调用await方法的线程被终端,则抛出异常
    if (Thread.interrupted()) {
        throw new InterruptedException(toString());
    }
	
    // 死锁鉴定
    checkDeadLock();

    //synchronized-double-check
    synchronized (this) {
        while (!isDone()) {
            // 等待线程计数+1
            incWaiters();
            try {
                // 阻塞等待任务执行完成
                wait();
            } finally {
                // 等待线程计数-1
                decWaiters();
            }
        }
    }
    return this;
}

isDone()表示任务是否执行完成:执行完成直接返回;没有完成,调用Object的wait()方法,线程陷入同步队列阻塞等待,注意这里的锁对象是Promise本身。
这里还有个点需要注意:checkDeadLock()用于进行死锁鉴定,在死锁发送前抛出异常,从而防止死锁。
DefaultPromise的线程池对象不能阻塞在该Promise对象上,因为同步队列中的阻塞的线程依赖该线程池调用notifyAll唤醒,否则陷入死锁。
[2] sync方法
与await一样,sync也提供了响应中断和不响应中断两个方法, 这里也仅以sync()为例进行介绍:

@Override
public Promise<V> sync() throws InterruptedException {
    await();
    rethrowIfFailed();
    return this;
}

@Override
public Promise<V> syncUninterruptibly() {
    awaitUninterruptibly();
    rethrowIfFailed();
    return this;
}

逻辑较为简单,调用await()阻塞直到任务执行完成,任务异常时抛出异常。

[3] get方法
DefaultPromise提供了三个版本的get方法: get()、get(timeout)、getNow(). 其中getNow()不阻塞,当任务未完成时返回空,而get()和get(timeout)当任务未完成时阻塞等待,区别是get(timeout)超时后主动醒来,而 get()持续陷入阻塞状态知道被notify/notifyAll唤醒。
这里以get()为例进行介绍:

@Override
public V get() throws InterruptedException, ExecutionException {
    Object result = this.result;
    // 任务未完成,则阻塞等待任务完成
    if (!isDone0(result)) {
        await();
        result = this.result;
    }
    // 结果为空
    if (result == SUCCESS || result == UNCANCELLABLE) {
        return null;
    }
    // 任务设置为异常状态时,抛出异常
    Throwable cause = cause0(result);
    if (cause == null) {
        return (V) result;
    }
    if (cause instanceof CancellationException) {
        throw (CancellationException) cause;
    }
    throw new ExecutionException(cause);
}

逻辑较为简单,如果任务已完成,则直接返回result或抛出异常(任务处于失败状态), 否则调用await()阻塞等待任务完成后被唤醒,再返回result或抛出异常。

[4] setSuccess和trySuccess

@Override
public Promise<V> setSuccess(V result) {
    if (setSuccess0(result)) {
        return this;
    }
    throw new IllegalStateException("complete already: " + this);
}

@Override
public boolean trySuccess(V result) {
    return setSuccess0(result);
}

setSuccess和trySuccess通过调用setSuccess0设置正常结果值,区别是setSuccess设值失败时会抛出异常,而trySuccess不会抛出异常。进一步查看setSuccess0逻辑:

private boolean setSuccess0(V result) {
    return setValue0(result == null ? SUCCESS : result);
}

private boolean setValue0(Object objResult) {
    if (RESULT_UPDATER.compareAndSet(this, null, objResult) || RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
        if (checkNotifyWaiters()) {
            notifyListeners();
        }
        return true;
    }
    return false;
}

setSuccess0调用setValue0实现设值功能,核心是checkNotifyWaiters()和notifyListeners(),前者是唤醒因获取执行结构而阻塞的线程,后者是回调监听器。外层有一个判断逻辑,只有result的值为null或者UNCANCELLABLE时才会执行唤醒和调用监听器的逻辑,否则直接返回false.
即,只有第一次设置才会返回true(注意:设置Promise为不可取消状态 不属于设值,isDone仍返回false), 其他场景返回false.

继续查看checkNotifyWaiters:

private synchronized boolean checkNotifyWaiters() {
    if (waiters > 0) {
        notifyAll();
    }
    return listeners != null;
}

如果有因获取Future执行结果而阻塞的线程,则通过notifyAll唤醒。
继续查看notifyListeners():

private void notifyListeners() {
    EventExecutor executor = executor();
    if (executor.inEventLoop()) {
        final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
        final int stackDepth = threadLocals.futureListenerStackDepth();
        if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
            threadLocals.setFutureListenerStackDepth(stackDepth + 1);
            try {
                notifyListenersNow();
            } finally {
                threadLocals.setFutureListenerStackDepth(stackDepth);
            }
            return;
        }
    }

    safeExecute(executor, new Runnable() {
        @Override
        public void run() {
            notifyListenersNow();
        }
    });
}

notifyListeners()方法的核心逻辑是notifyListenersNow(),notifyListenersNow已在前文介绍过,依次调用监听器方法。

这里需要注意的是notifyListenersNow()的调用由Promise关联的线程池执行,在Netty中调用监听器的逻辑由Promise关联的NioEventLoop执行(唤醒阻塞线程也是)。

[5] setFailure和tryFailure

@Override
public Promise<V> setFailure(Throwable cause) {
    if (setFailure0(cause)) {
        return this;
    }
    throw new IllegalStateException("complete already: " + this, cause);
}

@Override
public boolean tryFailure(Throwable cause) {
    return setFailure0(cause);
}

setFailure和tryFailure通过调用setFailure0设置异常结果值,区别是setFailure设值失败时会抛出异常,而tryFailure不会抛出异常。进一步查看setFailure0逻辑:

private boolean setFailure0(Throwable cause) {
    return setValue0(new CauseHolder(checkNotNull(cause, "cause")));
}

private boolean setValue0(Object objResult) {
    if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
        RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
        if (checkNotifyWaiters()) {
            notifyListeners();
        }
        return true;
    }
    return false;
}

setFailure0调用setValue0实现设值功能,逻辑与setSuccess和trySuccess相同。

4.ChannelFuture和ChannelPromise和DefaultChannelPromise

4.1 ChannelFuture

public interface ChannelFuture extends Future<Void> {
    Channel channel();
    boolean isVoid();//Ignore	
	// ... 重写listener/await/sync方法的返回结果为ChannelFuture
}

ChannelFuture相对于Future引入了channel的概念,新增了一个channel()方法用于返回Future绑定的通道对象。
并将所有的listener/await/sync方法的返回结果重写为ChannelFuture类型。

4.2 ChannelPromise

public interface ChannelPromise extends ChannelFuture, Promise<Void> {
	// 引入无参的setSuccess和trySuccess方法
    ChannelPromise setSuccess();
    boolean trySuccess();
    ChannelPromise unvoid();//Ignore
	// ... 重写listener/await/sync方法的返回结果为ChannelPromise
}

ChannelPromise类继承了ChannelFuture和Promise接口,成为具有通道能力的Promise,同时引入了无参的setSuccess()和trySuccess()方法。然后将所有的listener/await/sync方法的返回结果重写为ChannelPromise类型。

4.3 DefaultChannelPromise

DefaultChannelPromise作为DefaultPromise接口的实现类,也作为DefaultPromise的子类,是一个具备通道能力的Promise。内部维持了一个Channel通道对象,可通过channel()返回返回该对象。

public class DefaultChannelPromise extends DefaultPromise<Void> implements ChannelPromise, FlushCheckpoint {

    private final Channel channel;
    @Override
    public Channel channel() {
        return channel;
    }
    
    public DefaultChannelPromise(Channel channel) {
        this.channel = checkNotNull(channel, "channel");
    }
	
    @Override
    protected EventExecutor executor() {
        EventExecutor e = super.executor();
        if (e == null) {
            return channel().eventLoop();
        } else {
            return e;
        }
    }
    
    // ...
}

Netty构造DefaultChannelPromise时,使用public DefaultChannelPromise(Channel channel)构造函数,而没有给父类设置线程池,因此executor()方法返回的线程池对象实际是通道绑定的NioEventLoop.
上述为DefaultChannelPromise关于实现channel的特性,其他方法如trysuccess/setsucess等均调用父类(DefaultPromise)方法实现,并返回当前对象,以setSuccess为例:

@Override
public ChannelPromise setSuccess(Void result) {
    super.setSuccess(result);
    return this;
}

5.CloseFuture和PendingRegistrationPromise

CloseFuture和PendingRegistrationPromise作为DefaultChannelPromise的子类,在DefaultChannelPromise的基础上分别进行了定制。

5.1 CloseFuture

static final class CloseFuture extends DefaultChannelPromise {
	CloseFuture(AbstractChannel ch) {super(ch);}

	@Override public ChannelPromise setSuccess() { throw new IllegalStateException();}
	@Override public ChannelPromise setFailure(Throwable cause) {throw new IllegalStateException();}
	@Override public boolean trySuccess() {throw new IllegalStateException();}
	@Override public boolean tryFailure(Throwable cause) {throw new IllegalStateException();}

	boolean setClosed() {
		return super.trySuccess();
	}
}

CloseFuture禁用了所有Promise提供的设置结果的方法,并单独提供了一个setClosed()方法,只有该方法调用才会设置Promise执行结果为完成。
在Netty中启动Netty的线程会阻塞在该CloseFuture上,直到注册异常或者关闭Netty时才会从阻塞中恢复。

5.2 GlobalEventExecutor

public static final GlobalEventExecutor INSTANCE = new GlobalEventExecutor();

static final class PendingRegistrationPromise extends DefaultChannelPromise {
	private volatile boolean registered;
	
	void registered() {
		registered = true;
	}

	PendingRegistrationPromise(Channel channel) {
		super(channel);
	}

	@Override
	protected EventExecutor executor() {
		if (registered) {
			return super.executor();
		}
		// The registration failed so we can only use the GlobalEventExecutor as last resort to notify.
		return GlobalEventExecutor.INSTANCE;
	}
}

PendingRegistrationPromise引入了注册状态的概念,对应在内部维持了一个registered布尔变量以及获取注册状态的方法registered()。
从Netty功能的角度考虑,通道只有注册成功后,才会绑定NioEventLoop线程,DefaultChannelPromise的executor()才会返回线程对象,否则(注册失败时)返回空对象。PendingRegistrationPromise对注册异常场景提供了逃生门,使用GlobalEventExecutor.INSTANCE(替代NioEventLoop)唤醒阻塞在当前Promise上的线程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2112660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GBase8sV8.8安装指南

目录 一、下载 Gbase 安装包二、安装预置条件1.确保安装包和平台适配2.安装依赖包&#xff1a;jdk(1.6版本以上)、unzip、libaio、libgcc、libstdc、ncurses、pam&#xff0c;如果缺失请提前安装 三、上传包并解压四、安装五、登录并创建数据库六、启动停止数据库七、常见问题八…

虚拟机ubuntu与主机共享文件夹

现在主机&#xff08;windows&#xff09;上新建一个共享文件夹 打开虚拟机 按下面操作打开共享文件夹 进入虚拟机的系统 cd /mnt/hgfs 如果报错 可以按下面的解决 挂载一下 sudo mount -t fuse.vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other 如果显示不存在这个文…

session机制

场景&#xff1a;当众多用户访问网站&#xff0c;发出HTTP请求&#xff0c;那么网站是如何判断哪个HTTP请求对应的是哪个用户 &#xff1f; 作用&#xff1a;用于服务端区分用户。 当用户使用客户端登录时&#xff0c;服务端会进行验证&#xff0c;验证通过后会为这次登录创建…

剖析Cookie的工作原理及其安全风险

Cookie的工作原理主要涉及到HTTP协议中的状态管理。HTTP协议本身是无状态的&#xff0c;这意味着每次请求都是独立的&#xff0c;服务器不会保留之前的请求信息。为了在无状态的HTTP协议上实现有状态的会话&#xff0c;引入了Cookie机制。 1. Cookie定义 Cookie&#xff0c;也…

EMC测试

传导干扰测试&#xff1a; 现场实录CE传导骚扰电压测试&#xff0c;硬件环境&#xff1a; R&S EPL1000 EMI测量接收机&#xff08;支持时域测试&#xff09; R&S ENV216人工电源网络 R&S ELEKTRA 测试软件 黑色底板&#xff0c;不写丝印&#xff0c;0402封装平行排…

Tomcat服务详解

一、部署Tomcat服务器 JDK安装官方网址&#xff1a;https://www.oracle.com/cn/java Tomcat安装官方网址&#xff1a;Apache Tomcat - Welcome! 安装JDK 1.获取安装包 wget https://download.oracle.com/otn/java/jdk/8u411-b09/43d62d619be4e416215729597d70b8ac/jdk-8u41…

【工程测试技术】第13章 流体参量测量

目录 第13章 流体参量测量 13.1压力的测量 13.1.1 弹性式压力敏感元件 1. 波登管 2. 膜片和膜盒 3. 波纹管 13.1.2 常用压力传感器 1. 应变式压力传感器 2. 压阻式压力传感器 3. 压电式压力传感器 4. 电容式压力传感器 5. 谐振式压力传感器 6. 位移式压力传感器 (1)…

整型数组按个位值排序

题目描述 给定一个非空数组(列表)&#xff0c;其元素数据类型为整型&#xff0c;请按照数组元素十进制最低位从小到大进行排序&#xff0c;十进制最低位相同的元司 相对位置保持不变。 当数组元素为负值时&#xff0c;十进制最低位等同于去除符号位后对应十进制值最低位。 输…

吐血整理 ChatGPT 3.5/4.0 新手使用手册~ 【2024.09.04 更新】

以前我也是通过官网使用&#xff0c;但是经常被封号&#xff0c;就非常不方便&#xff0c;后来有朋友推荐国内工具&#xff0c;用了一阵之后&#xff0c;发现&#xff1a;稳定方便&#xff0c;用着也挺好的。 最新的 GPT-4o、4o mini&#xff0c;可搭配使用~ 1、 最新模型科普&…

VisualStudio环境搭建C++

Visual Studio环境搭建 说明 C程序编写中&#xff0c;经常需要链接头文件(.h/.hpp)和源文件(.c/.cpp)。这样的好处是&#xff1a;控制主文件的篇幅&#xff0c;让代码架构更加清晰。一般来说头文件里放的是类的申明&#xff0c;函数的申明&#xff0c;全局变量的定义等等。源…

Java面试题·解释题·框架部分

系列文章目录 Java面试题解释题总体概括 Java面试题解释题JavaSE部分 Java面试题解释题框架部分 文章目录 系列文章目录前言一、MyBatis1. 请你介绍MyBatis框架2. MyBatis框架的核心思想是什么&#xff1f;3. MyBatis的核心配置文件中常用的子标签有哪些&#xff1f;4. mapper…

饲料加工机器设备有哪些组成部分

在快速发展的畜牧业中&#xff0c;饲料加工作为支撑养殖业的重要环节&#xff0c;其效率与品质直接影响着养殖业的成本效益与动物健康。随着科技的进步&#xff0c;饲料加工机器设备也在不断升级&#xff0c;为养殖行业带来了变革。一、智能化粉碎机&#xff1a;细度可调&#…

Unity Adressables 使用说明(五)在运行时使用 Addressables(Use Addressables at Runtime)

一旦你将 Addressable assets 组织到 groups 并构建到 AssetBundles 中&#xff0c;就需要在运行时加载、实例化和释放它们。 Addressables 使用引用计数系统来确保 assets 只在需要时保留在内存中。 Addressables 初始化 Addressables 系统在运行时第一次加载 Addressable …

SimD:基于相似度距离的小目标检测标签分配

摘要 https://arxiv.org/pdf/2407.02394 由于物体尺寸有限且信息不足&#xff0c;小物体检测正成为计算机视觉领域最具挑战性的任务之一。标签分配策略是影响物体检测精度的关键因素。尽管已经存在一些针对小物体的有效标签分配策略&#xff0c;但大多数策略都集中在降低对边界…

怎么利用XML发送物流快递通知短信

现如今短信平台越来越普遍了&#xff0c;而短信通知也分很多种&#xff0c;例如服务通知、订单通知、交易短信通知、会议通知等。而短信平台在物流行业通知这一块作用也很大。在家时:我们平时快递到了&#xff0c;如果电话联系不到本人&#xff0c;就会放到代收点&#xff0c;然…

正负极层数更新器

文件名&#xff1a;dcs_tkinter.py import tkinter as tk from tkinter import messagebox import redis# 连接Redis r redis.Redis(hostlocalhost, port6379, db0)def update_redis_and_display():try:# 从输入框获取值positive_layers int(entry_positive.get())negative_…

2024国赛数学建模C题论文:基于优化模型的农作物的种植策略

大家可以查看一下35页&#xff0c;包含结构完整&#xff0c;数据完整的C题论文&#xff0c;完整论文见文末名片 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xf…

Nexus配置npm私服

1&#xff0c;配置npm-hub 2&#xff0c;配置proxy-npm 3&#xff0c;配置group-npm 4&#xff0c;配置local-npm 5&#xff0c;配置淘宝

Java语言程序设计基础篇_编程练习题**17.20 (二进制编辑器)

目录 题目&#xff1a;**17.20 (二进制编辑器) 代码示例 结果展示 题目&#xff1a;**17.20 (二进制编辑器) 编写一个GUI应用程序&#xff0c;让用户在文本域输入一个文件名&#xff0c;然后单击回车键&#xff0c;在文本区域显示它的二进制表示形式。用户也可以修改这个二…

每日一题~cf 970 div3 (A思维,B小模拟,C二分,D排列数建图成环,E 26个字母暴力+前缀和,F 逆元,G 数论gcd )

A 题意&#xff1a; 有 a 个1 ,b 个2.问是否能将这些数划分为两个数值相等的集合。 输出 YES 或者 NO —————— 问题等价于 将数组 分成两个数值相同的数组。所以sum 应该是偶数。也就是说 1 的个数是偶数。在i1的个数是偶数的情况下&#xff0c;将 2 分成两份&#xff0c;…