阿里技术专家加多:Java异步编程实战之基于JDK中的Future实现异步编程

article/2025/9/14 14:53:26

正文共:14244 字 8 图

预计阅读时间: 36 分钟


本节内容摘自《Java异步编程实战》中的一小节。

一、前言

本节主要讲解如何使用JDK中的Future实现异步编程,这包含如何使用FutureTask实现异步编程以及其内部实现原理以及FutureTask的局限性。

二、 JDK 中的Future

在Java并发包(JUC包)中Future代表着异步计算结果,Future中提供了一些列方法用来检查计算结果是否已经完成,还提供了同步等待任务执行完成的方法,以及获取计算结果的方法等。当计算结果完成时只能通过提供的get系列方法来获取结果,如果使用了不带超时时间的get方法则在计算结果完成前,调用线程会被一直阻塞。另外计算任务是可以使用cancle方法来取消的,但是一旦一个任务计算完成,则不能再被取消了。

首先我们看下Future接口的类图结构如图3-1-1:

图3-1-1Future类图

如上图3-1-1Future类总共就有5个接口方法,下面我们来一一讲解:

  • V get() throws InterruptedException, ExecutionException :等待异步计算任务完成,并返回结果;如果当前任务计算还没完成则会阻塞调用线程直到任务完成;如果在等待结果的过程中有其他线程取消了该任务,则调用线程抛出CancellationException异常;如果在等待结果的过程中有其他线程中断了该线程,则调用线程抛出InterruptedException异常;如果任务计算过程中抛出了异常,则调用线程会抛出ExecutionException异常。

  • V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException:相比get()方法多了超时时间,不同在于线程调用了该方法后在任务结果没有计算出来前调用线程不会一直被阻塞,而是会在等待timeout个unit单位的时间后抛出TimeoutException异常后返回。添加超时时间这避免了调用线程死等的情况,让调用线程可以及时释放。

  • boolean isDone():如果计算任务已经完成则返回true,否则返回false,需要注意的是任务完成是指任务正常完成了或者由于抛出异常而完成了或者任务被取消了。

  • boolean cancel(boolean mayInterruptIfRunning) :尝试取消任务的执行;如果当前任务已经完成或者任务已经被取消了,则尝试取消任务会失败;如果任务还没被执行时候,调用了取消任务,则任务将永远不会被执行;如果任务已经开始运行了,这时候取消任务,则参数mayInterruptIfRunning将决定是否要将正在执行任务的线程中断,如果为true则标识要中断,否则标识不中断;当调用取消任务后,在调用isDone()方法,后者会返回true,随后调用isCancelled()方法也会一直返回true;该方法会返回false,如果任务不能被取消,比如任务已经完成了,任务已经被取消了。

  • boolean isCancelled():如果任务在被执行完毕前被取消了,则该方法返回true,否则返回false。

三 JDK中的FutureTask

3.1 FutureTask 概述

FutureTask代表了一个可被取消的异步计算任务,该类实现了Future接口,比如提供了启动和取消任务、查询任务是否完成、获取计算结果的接口。

FutureTask任务的结果只有当任务完成后才能获取,并且只能通过get系列方法获取,当结果还没出来时候,线程调用get系列方法会被阻塞;另外一旦任务被执行完成,任务不能被重启,除非运行时候使用了runAndReset方法;FutureTask中的任务可以是Callable类型,也可以是Runnable类型(因为FutureTask实现了Runnable接口),FutureTask类型的任务可以被提交到线程池执行。

我们修改上节的例子如下:

public class AsyncFutureExample {public static String doSomethingA() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--- doSomethingA---");return "TaskAResult";}public static String doSomethingB() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--- doSomethingB---");return "TaskBResult";}public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();// 1.创建future任务FutureTask<String> futureTask = new FutureTask<String>(() -> {String result = null;try {result = doSomethingA();} catch (Exception e) {e.printStackTrace();}return result;});// 2.开启异步单元执行任务AThread thread = new Thread(futureTask, "threadA");thread.start();// 3.执行任务BString taskBResult = doSomethingB();// 4.同步等待线程A运行结束String taskAResult = futureTask.get();//5.打印两个任务执行结果System.out.println(taskAResult + " " + taskBResult); System.out.println(System.currentTimeMillis() - start);}
}
  • 如上代码doSomethingA和doSomethingB方法都是有返回值的任务,main函数内代码1创建了一个异步任务futureTask,其内部执行任务doSomethingA。

  • 代码2则创建了一个线程,并且以futureTask为执行任务,并且启动;代码3使用main线程执行任务doSomethingB,这时候任务doSomethingB和doSomethingA是并发运行的,等main函数运行doSomethingB完毕后,执行代码4同步等待doSomethingA任务完成,然后代码5打印两个任务的执行结果。

  • 如上可知使用FutureTask可以获取到异步任务的结果。

当然我们也可以把FutureTask提交到线程池来执行,使用线程池运行方式代码如下:

    // 0自定义线程池private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();private final static ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(5),new ThreadPoolExecutor.CallerRunsPolicy());public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();// 1.创建future任务FutureTask<String> futureTask = new FutureTask<String>(() -> {String result = null;try {result = doSomethingA();} catch (Exception e) {e.printStackTrace();}return result;});// 2.开启异步单元执行任务APOOL_EXECUTOR.execute(futureTask);// 3.执行任务BString taskBResult = doSomethingB();// 4.同步等待线程A运行结束String taskAResult = futureTask.get();// 5.打印两个任务执行结果System.out.println(taskAResult + " " + taskBResult);System.out.println(System.currentTimeMillis() - start);}

如上可知代码0创建了一个线程池,代码2添加异步任务到线程池,这里我们是调用了线程池的execute方法把futureTask提交到线程池的,其实下面代码与上面是等价的:

    public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();// 1.开启异步单元执行任务AFuture<String> futureTask = POOL_EXECUTOR.submit(() -> {String result = null;try {result = doSomethingA();} catch (Exception e) {e.printStackTrace();}return result;});// 2.执行任务BString taskBResult = doSomethingB();// 3.同步等待线程A运行结束String taskAResult = futureTask.get();// 4.打印两个任务执行结果System.out.println(taskAResult + " " + taskBResult);System.out.println(System.currentTimeMillis() - start);}

如上代码1我们调用了线程池的submit方法提交了一个任务到线程池,然后返回了一个FutureTask对象。

3.2 FutureTask的类图结构:

由于FutureTask在异步编程领域还是比较重要的,所以我们有必要探究下其原理,以便加深对异步的理解,首先我们来看下其类图结构如图3-2-2-1:图3-2-2-1 FutureTask的类图

  • 如上时序图3-2-2-1FutureTask实现了Future接口的所有方法,并且实现了Runnable接口,所以其是可执行任务,可以投递到线程池或者线程来执行。

  • FutureTask中变量state是一个使用volatile关键字修饰(用来解决多线程下内存不可见问题,具体可以参考《Java并发编程之美》一书)的int类型,用来记录任务状态,任务状态枚举值如下:

    private static final int NEW          = 0;private static final int COMPLETING   = 1;private static final int NORMAL       = 2;private static final int EXCEPTIONAL  = 3;private static final int CANCELLED    = 4;private static final int INTERRUPTING = 5;private static final int INTERRUPTED  = 6;

一开始任务状态会被初始化为NEW;当通过set、 setException、 cancel函数设置任务结果时候,任务会转换为终止状态;在任务完成过程中,设置任务状态会变为COMPLETING(当结果被使用set方法设置时候),也可能会经过INTERRUPTING状态(当使用cancel(true)方法取消任务并中断任务时候);当任务被中断后,任务状态为INTERRUPTED;当任务被取消后,任务状态为CANCELLED;当任务正常终止时候,任务状态为NORMAL;当任务执行异常后,任务会变为EXCEPTIONAL状态。

另外在任务运行过程中,任务可能的状态转换路径如下:

  • NEW -> COMPLETING -> NORMAL :正常终止流程转换

  • NEW -> COMPLETING -> EXCEPTIONAL:执行过程中发生异常流程转换

  • NEW -> CANCELLED:任务还没开始就被取消

  • NEW -> INTERRUPTING -> INTERRUPTED:任务被中断

从上述转换可知,任务最终只有四种终态:NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTED,另外可知任务的状态值是从上到下递增的。

  • 类图中callable是有返回值的可执行任务,创建FutureTask对象时候,可以通过构造函数传递该任务。

  • 类图中outcome是任务运行的结果,可以通过get系列方法来获取该结果,另外outcome这里没有被修饰为volatile,是因为变量state已经被使用volatile修饰了,这里是借用volatile的内存语义来保证写入outcome时候会把值刷新到主内存,读取时候会从主内存读取,从而避免多线程下内存不可见问题(可以参考《Java并发编程之美》一书)。

  • 类图中runner变量,记录了运行该任务的线程,这个是在FutureTask的run方法内使用CAS函数设置的。

  • 类图中waiters变量是一个WaitNode节点,使用Treiber stack实现的无锁栈,栈顶元素就是使用waiters代表,栈用来记录所有等待任务结果的线程节点,其定义为:

    static final class WaitNode {volatile Thread thread;volatile WaitNode next;WaitNode() { thread = Thread.currentThread(); }}

可知其是一个简单的链表,用来记录所有等待结果而被阻塞的线程。

最后我们看下其构造函数:

    public FutureTask(Callable<V> callable) {if (callable == null)throw new NullPointerException();this.callable = callable;this.state = NEW;       }

如上代码可知构造函数内保存了传递的callable任务到callable变量,并且设置任务状态为NEW,这里由于state为volatile修饰,所以写入state的值可以保证callable的写入也会被刷入主内存,这避免了多线程下内存不可见性。

另外还有一个构造函数:

    public FutureTask(Runnable runnable, V result) {this.callable = Executors.callable(runnable, result);this.state = NEW;     }

该函数传入一个Runnable类型的任务,由于该任务是不具有返回值的,所以这里使用Executors.callable方法进行适配,适配为Callable类型的任务.

Executors.callable(runnable, result);把Runnable类型任务转换为了callable:

    public static <T> Callable<T> callable(Runnable task, T result) {if (task == null)throw new NullPointerException();return new RunnableAdapter<T>(task, result);}static final class RunnableAdapter<T> implements Callable<T> {final Runnable task;final T result;RunnableAdapter(Runnable task, T result) {this.task = task;this.result = result;}public T call() {task.run();return result;}}

如上可知使用了适配器模式来做转换。

另外FutureTask中使用了UNSAFE机制来操作内存变量:

 private static final sun.misc.Unsafe UNSAFE;private static final long stateOffset;//state变量的偏移地址private static final long runnerOffset;//runner变量的偏移地址private static final long waitersOffset;//waiters变量的偏移地址static {try {//获取UNSAFE的实例UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> k = FutureTask.class;//获取变量state的偏移地址stateOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("state"));//获取变量runner的偏移地址runnerOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("runner"));//获取变量waiters变量的偏移地址waitersOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("waiters"));} catch (Exception e) {throw new Error(e);}
}

如上代码分别获取了FutureTask中几个变量在FutureTask对象内的内存地址偏移量,以便实现中使用UNSAFE的CAS操作来操作这些变量。

3.3 FutureTask的run() 方法

该方法是任务的执行体,线程是调用该方法来具体运行任务的,如果任务没有被取消,则该方法会运行任务,并且设置结果到outcome变量里面,其代码:

public void run() {//1.如果任务不是初始化的NEW状态,或者使用CAS设置runner为当前线程失败,则直接返回if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;//2.如果任务不为null,并且任务状态为NEW,则执行任务try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;//2.1执行任务,如果OK则设置ran标记为truetry {result = c.call();ran = true;} catch (Throwable ex) {//2.2执行任务出现异常,则标记false,并且设置异常result = null;ran = false;setException(ex);}//3.任务执行正常,则设置结果if (ran)set(result);}} finally {runner = null;int s = state;//4.为了保证调用cancel(true)的线程在该run方法返回前中断任务执行的线程if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}
}
private void handlePossibleCancellationInterrupt(int s) {//为了保证调用cancel在该run方法返回前中断任务执行的线程//这里使用Thread.yield()让run方法执行线程让出cpu执行权,以便让//cancel(true)的线程执行cancel(true)中的代码中断任务线程if (s == INTERRUPTING)while (state == INTERRUPTING)Thread.yield(); // wait out pending interrupt
}
  • 如上代码1,如果任务不是初始化的NEW状态,或者使用CAS设置runner为当前线程失败,则直接返回;这个可以防止同一个FutureTask对象被提交给多个线程来执行,导致run方法被多个线程同时执行造成混乱。

  • 代码2,如果任务不为null,并且任务状态为NEW,则执行任务,其中代码2.1调用c.call()具体执行任务,如果任务执行OK,则调用set方法把结果记录到result,并设置ran为true;否则执行任务过程中抛出异常则设置result为null,ran为false,并且调用setException设置异常信息后,任务就处于终止状态,其中setException代码如下:

protected void setException(Throwable t) {//2.2.1if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {outcome = t;UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state//2.2.1.1finishCompletion();}
}

如上代码,使用CAS尝试设置任务状态state为COMPLETING,如果CAS成功则把异常信息设置到outcome变量,并且设置任务状态为EXCEPTIONAL终止状态,然后调用finishCompletion,其代码:

private void finishCompletion() {//a遍历链表节点for (WaitNode q; (q = waiters) != null;) {//a.1 CAS设置当前waiters节点为nullif (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {//a.1.1for (;;) {//唤醒当前q节点对应的线程Thread t = q.thread;if (t != null) {q.thread = null;LockSupport.unpark(t);}//获取q的下一个节点WaitNode next = q.next;if (next == null)break;q.next = null; //help gcq = next;}break;}}//b。所有阻塞的线程都被唤醒后,调用done方法done();callable = null;        // callable设置为null
}

如上代码比较简单,就是当任务已经处于终态后,激活waiters链表中所有由于等待获取结果而被阻塞的线程,并从waiters链表中移除他们,等所有由于等待该任务结果的线程被唤醒后,调用done()方法,done默认实现为空实现。

上面我们讲了当任务执行过程中出现异常后如何处理的,下面我们看代码3,当任务是正常执行完毕后set(result)的实现:

protected void set(V v) {//3.1if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {outcome = v;UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final statefinishCompletion();}
}

如上代码3.1,使用CAS尝试设置任务状态state为COMPLETING,如果CAS成功则把任务结果设置到outcome变量,并且设置任务状态为NORMAL终止状态,然后调用finishCompletion唤醒所有因为等待结果而被阻塞的线程。

3.4 FutureTask的get()方法

等待异步计算任务完成,并返回结果;如果当前任务计算还没完成则会阻塞调用线程直到任务完成;如果在等待结果的过程中有其他线程取消了该任务,则调用线程会抛出CancellationException异常;如果在等待结果的过程中有线程中断了该线程,则抛出InterruptedException异常;如果任务计算过程中抛出了异常,则会抛出ExecutionException异常。

其代码如下:

public V get() throws InterruptedException, ExecutionException {//1.获取状态,如有需要则等待int s = state;if (s <= COMPLETING)//等待任务终止s = awaitDone(false, 0L);//2.返回结果return report(s);
}
  • 如上代码1获取任务的状态,如果任务状态的值小于等于COMPLETING则说明任务还没有被完成,所以调用awaitDone挂起调用线程。

  • 代码2如果任务已经被完成,则返回结果。下面我们看awaitDone方法实现:

private int awaitDone(boolean timed, long nanos)throws InterruptedException {//1.1超时时间final long deadline = timed ? System.nanoTime() + nanos : 0L;WaitNode q = null;boolean queued = false;//1.2 循环,等待任务完成for (;;) {//1.2.1任务被中断,则移除等待线程节点,抛出异常if (Thread.interrupted()) {removeWaiter(q);throw new InterruptedException();}//1.2.2 任务状态>COMPLETING说明任务已经终止int s = state;if (s > COMPLETING) {if (q != null)q.thread = null;return s;   }//1.2.3任务状态为COMPLETINGelse if (s == COMPLETING) // cannot time out yetThread.yield();//1.2.4为当前线程创建节点else if (q == null)q = new WaitNode();//1.2.5 添加当前线程节点到链表else if (!queued)queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);//1.2.6 设置了超时时间else if (timed) {nanos = deadline - System.nanoTime();if (nanos <= 0L) {removeWaiter(q);return state;}LockSupport.parkNanos(this, nanos);}//1.2.7没有设置超时时间elseLockSupport.park(this);}
}
  • 如上代码1.1获取设置的超时时间,如果传递的timed为false说明没有设置超时时间,则deadline设置为0

  • 代码1.2无限循环等待任务完成,其中代码1.2.1如果发现当前线程被中断则从等待链表中移除当前线程对应的节点(如果队列里面有该节点的话),然后抛出InterruptedException异常;代码1.2.2如果发现当前任务状态大于COMPLETING说明任务已经进入了终态(可能是NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTED中的一种),则把执行任务的线程的引用设置为null,并且返回结果;

  • 代码1.2.3如果当前任务状态为COMPLETING说明任务已经接近完成了,就差设置结果到outCome了,则这时候让当前线程放弃CPU执行,意在让任务执行线程获取到CPU然后让任务状态从COMPLETING转换到终态NORMAL,这样可以避免当前调用get系列的方法的线程被挂起,然后在被唤醒的开销;

  • 代码1.2.4如果当前q为null,则创建一个与当前线程相关的节点,代码1.2.5如果当前线程对应节点还没放入到waiters管理的等待列表,则使用CAS操作放入;

  • 代码1.2.6如果设置了超时时间则使用LockSupport.parkNanos(this, nanos)让当前线程挂起deadline时间,否则会调用 LockSupport.park(this);让线程一直挂起直到其他线程调用了unpark方法,并且以当前线程为参数(比如finishCompletion()方法)。

另外带超时参数的V get(long timeout, TimeUnit unit)方法与get()方法类似,只是添加了超时时间,这里不再累述。

3.5 FutureTask的cancel(boolean mayInterruptIfRunning)方法

尝试取消任务的执行,如果当前任务已经完成或者任务已经被取消了,则尝试取消任务会失败;如果任务还没被执行时候,调用了取消任务,则任务将永远不会被执行;如果任务已经开始运行了,这时候取消任务,则参数mayInterruptIfRunning将决定是否要将正在执行任务的线程中断,如果为true则标识要中断,否则标识不中断;

当调用取消任务后,在调用isDone()方法,后者会返回true,随后调用isCancelled()方法也会一直返回true;该方法会返回false,如果任务不能被取消,比如任务已经完成了,任务已经被取消了。

cancel方法的代码如下:

public boolean cancel(boolean mayInterruptIfRunning) {//1.如果任务状态为New则使用CAS设置任务状态为INTERRUPTING或者CANCELLEDif (!(state == NEW &&UNSAFE.compareAndSwapInt(this, stateOffset, NEW,mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))return false;//2.如果设置了中断正常执行任务线程,则中断try {    if (mayInterruptIfRunning) {try {Thread t = runner;if (t != null)t.interrupt();} finally { // final stateUNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);}}} finally {//3.移除并激活所有因为等待结果而被阻塞的线程finishCompletion();}return true;
}
  • 如上代码1,如果任务状态为New则使用CAS设置任务状态为INTERRUPTING或者CANCELLED,如果mayInterruptIfRunning设置为了true则说明要中断正在执行任务的线程,则使用CAS设置任务状态为INTERRUPTING,否则设置为CANCELLED;如果CAS失败则直接返回false。

  • 如果CAS成功,则说明当前任务状态已经为INTERRUPTING或者CANCELLED,如果mayInterruptIfRunning为true则中断执行任务的线程,然后设置任务状态为INTERRUPTED。

  • 最后代码3移除并激活所有因为等待结果而被阻塞的线程。

另外我们可以使用isCancelled()方法判断一个任务是否被取消了,使用isDone()方法判断一个任务是否处于终态了。

总结:当我们创建一个FutureTask时候,其任务状态初始化为NEW,当我们把任务提交到线程或者线程池后,会有一个线程来执行该FutureTask任务,具体是调用其run方法来执行任务。在任务执行过程中,我们可以在其他线程调用FutureTask的get()方法来等待获取结果,如果当前任务还在执行,则调用get的线程会被阻塞然后放入FutureTask内的阻塞链表队列;多个线程可以同时调用get方法,这些线程可能都会被阻塞到了阻塞链表队列。当任务执行完毕后会把结果或者异常信息设置到outcome变量,然后会移除和唤醒FutureTask内的阻塞链表队列里面的线程节点,然后这些由于调用FutureTask的get方法而被阻塞的线程就会被激活。

3.6 FutureTask的局限性

FutureTask虽然提供了用来检查任务是否执行完成、等待任务执行结果、获取任务执行结果的方法,但是这些特色并不足以让我们写出简洁的并发代码。比如它并不能清楚的表达出多个FutureTask之间的关系,另外为了从Future获取结果,我们必须调用get()方法,而该方法还是会在任务执行完毕前阻塞调用线程的,这明显不是我们想要的。

我们真正要想要的是:

  • 可以将两个或者多个异步计算结合在一起变成一个,这包含两个或者多个异步计算是相互独立的时候或者第二个异步计算依赖第一个异步计算结果的时候。

  • 对反应式编程的支持,也就是当任务计算完成后能进行通知,并且可以以计算结果作为一个行为动作的参数进行下一步计算,而不是仅仅提供调用线程以阻塞的方式获取计算结果。

  • 可以通过编程的方式手动设置(代码的方式)Future的结果;FutureTask则不可以让用户通过函数来设置其计算结果,而是其任务内部来进行设置。

  • 可以等多个Future对应的计算结果都出来后做一些事情

为了克服FutureTask的局限性,以及满足我们对异步编程的需要,JDK8中提供了CompletableFuture,CompletableFuture是一个可以通过编程方式显式的设置计算结果和状态以便让任务结束的Future,本书后面章节我们会具体讲解。

四、总结

《Java异步编程实战》一书是国内首本系统讲解Java异步编程的书籍,本书涵盖了Java中常见的异步编程场景:这包含单JVM内的异步编程、以及跨主机通过网络通讯的远程过程调用的异步调用与异步处理、Web请求的异步处理、以及常见的异步编程框架原理解析和golang语言内置的异步编程能力。识别下方二维码即可购买本书:

        
#专注技术人的成长#
精彩推荐1. 漫画:程序员真是太太太太太太太太有趣了!
2. 漫画:程序员真的是太太太太太太太太难了!3. 知道创宇杨冀龙:技术人的商业思维都是锤出来的,真实需求长在客户的KPI上
4. 漫画:35岁的IT何去何从?
5. 漫画:从修灯泡来看各种 IT 岗位,你是哪一种?

点我支持加多????


http://chatgpt.dhexx.cn/article/UMEmDbYm.shtml

相关文章

如何理解向量组的秩和矩阵的秩

1 向量组的轶指的是极大线性无关组中向量的个数 矩阵的轶是把一个矩阵分为行向量组和列向量组,这两个向量组的轶分别称为行轶和列轶.可以证明的是行轶和列轶相等,这就是矩阵的轶. 这里提醒一下就是: n-r为线性无关的解向量的个数,而r为极大无关组的个数 n-r也为基础解析向量的…

线性代数[矩阵的秩]

系列文章目录 第一章 线性代数[初等变换(一)] 第二章 线性代数[初等变换(二)] 第三章 线性代数[初等变换(三)] 提示&#xff1a;线性代数[矩阵的秩](林耀东&#xff0c;东叔讲解限定版) 文章目录 系列文章目录 文章目录 一、矩阵的“秩”是什么&#xff1f; 二、矩阵的秩 1.…

矩阵的秩-理解

求解形如上面的n元一次方程组的解法 n个未知数的方程组需要有n个“干货”的方程才能解出唯一个结果&#xff0c;这里的干货就是矩阵的秩。 即矩阵的秩回答了解得唯一性。 线性无关与线性有关&#xff1a; 例如&#xff1a; 此时矩阵的秩就是2&#xff0c;即矩阵的秩是矩阵中的…

【线性代数(9)】矩阵的秩

矩阵的秩 1 k阶子式和秩的定义2 矩阵的秩的定理3 有关秩的性质 手动反爬虫&#xff1a; 原博地址 知识梳理不易&#xff0c;请尊重劳动成果&#xff0c;文章仅发布在CSDN网站上&#xff0c;在其他网站看到该博文均属于未经作者授权的恶意爬取信息如若转载&#xff0c;请标明出…

矩阵秩的几何意义

矩阵的秩是什么&#xff1f; 文章目录 前言一、矩阵秩的定义&#xff1f;二、矩阵乘法的几何意义三、几何上理解矩阵的秩1.矩阵 A A A是方阵时2.矩阵 A A A是方阵时&#xff08;3*3&#xff09;3.矩阵 A A A非方阵时&#xff08;3*2&#xff09; 总结参考 前言 相信大家刚开始…

【线代】矩阵的秩和线性方程组的解的情况

行最简型矩阵&#xff1a;(也可以叫做行最简阶梯型矩阵,或者行简化阶梯型矩阵)&#xff0c;其特点是&#xff1a;非零行的首非零元为1&#xff0c;且这些非零元所在的列的其它元素都为0。所谓的行最简的意思就是对应的方程组是“最简单的”&#xff0c;就是说&#xff0c;对应的…

线性代数之矩阵秩的求法与示例详解

线性代数之矩阵秩的求法 K阶子式的定义 在mn的矩阵A中&#xff0c;任取k行、k列(k小于等于m、k小于等于n)&#xff0c;位于这些行和列交叉处的 个元素&#xff0c;在不改变原有次序的情况下组成的矩阵叫做矩阵A的k阶子式。 不难发现矩阵A有个 个k阶子式。 比如有矩阵A 比如…

矩阵低秩有何意义?

参考博客&#xff1a;https://blog.csdn.net/weixin_41894030/article/details/115699611 首先来思考&#xff0c;为什么叫“秩”&#xff1f; 举个例子就很容易理解&#xff0c;大家排队买票。如果大家互相不认识&#xff0c;那就会一个排一个&#xff0c;非常有秩序。然而&…

如何直观地理解矩阵的秩?

矩阵的秩可以直观地理解为筛眼的大小&#xff1a; 下面就来解释这句话是什么意思&#xff1f; 1 矩阵的作用 假设对于向量 x1 、 x2、 x3、x4 有&#xff1a; 上述关系可以用图像来表示&#xff0c;左侧的向量 x1 、 x2、 x3、x4&#xff0c;在 A 的作用下&#xff0c;变为了…

矩阵的秩(Rank)

定义 一个矩阵 A 的列秩是 A 的线性无关的纵列的极大数目。类似地&#xff0c;行秩是 A 的线性无关的横行的极大数目。矩阵的列秩和行秩总是相等的&#xff0c;因此它们可以简单地称作矩阵 A 的秩。通常表示为 r(A)&#xff0c;rank(A) 或 rk(A)。 可替代定义 用行列式定义…

如何理解矩阵的「秩」?

本文作者Heshawn&#xff0c;点击关注&#xff0c;转载需授权。 利益相关&#xff1a;知乎『线性代数』系列Live主讲人 小时候老师总告诉我们「要有n个方程才能确定地解出n个未知数」——这句话其 实是不严格的&#xff0c;如果你想确定地解出n个未知数&#xff0c;只有n个方程…

矩阵的秩及其求法

矩阵的秩及其求法 矩阵秩的概念k阶子式矩阵的秩 矩阵秩的求法1、子式判别法&#xff08;定义&#xff09;2、用初等行变换求矩阵的秩 满秩矩阵相关性质 矩阵秩的概念 k阶子式 定义1&#xff1a; 设 A ( a i j ) m n A(a_{ij})_{m\times n} A(aij​)mn​在 A A A中任取 k k …

PPM与mg/m3的转换公式

转载于; https://blog.csdn.net/zhuisaozhang1292/article/details/88082631 版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;https://blog.csdn.net/zhuisaozhang1292/a…

用stm32读取遥控器接收机PPM信号各通道值

引言 无人机遥控器接收机接收方式可以在遥控上设置成PPM模式和S.BUS模式&#xff0c;用示波器观察接收机信号输出引脚可以得到一系列的方波&#xff0c;很像PWM方波&#xff0c;而我们所需要的是七&#xff0c;八个或更多的通道信号&#xff0c;多路的PWM信号调制在同一通道上…

气体浓度PPM与mg/m3的换算关系

1&#xff09;换算方法之一&#xff1a;《空气和废气检测分析方法&#xff08;第四版增补版&#xff09;》&#xff08;中国环境科学出版社&#xff09;空气中气体污染物浓度的表示方法 空气中污染物的浓度是以单位体积内所含污染物的质量来表示&#xff0c;即毫克每立方米…

ppm与LSB含义,换算

n表示ADC位数 关于PPM与LSB 一、ppm&#xff1a;百万分之一 ppm是英文part per million的缩写&#xff0c;表示百万分之几&#xff0c;在不同的场合与某些物理量组合&#xff0c;常用于表示器件某个直流参数的精度。下面举例说明。 1.用于描述电压基准&#xff08;Voltage …

理学知识01-ppm、ppb、ppt换算

1. 浓度 浓度是分析化学中的一个名词。含义是以1升溶液中所含溶质的摩尔数表示的浓度。以单位体积里所含溶质的物质的量&#xff08;摩尔数&#xff09;来表示溶液组成的物理量&#xff0c;叫作该溶质的摩尔浓度&#xff0c;又称该溶质物质的量浓度。 2. 浓度单位 在文献阅读过…

元器件温度系数(ppm/℃)是什么?

温漂也称为零点漂移或者温度漂移&#xff0c;一般指环境温度变化时引起半导体参数的变化&#xff0c;这样会造成静态工作点的不稳定&#xff0c;使电路动态参数不稳定&#xff0c;甚至使电路无法正常工作。 温度系数是材料的物理属性随着温度变化而变化的速率。常用单位是&…

SPP、ASPP与PPM

SPP、ASPP与PPM SPPPPMASPP SPP SPP模块是何凯明大神在2015年的论文《Spatial Pyramid Pooling in Deep ConvolutionalNetworks for Visual Recognition》中被提出。 在R-CNN中需要固定输入图片的尺寸&#xff0c;因为卷积层后面的全连接层的结构是固定的。但在现实中&#xf…

PPM文件

PPM文件 简介文件分类及扩展举例 简介 PPM&#xff08;Portable Pixmap Format&#xff09;是一种简单的图像格式&#xff0c;仅包含格式、图像宽高、bit数等信息和图像数据。 用txt打开.ppm文件的话&#xff0c;文件内容会如下所示: 文件分类及扩展 除开PPM还有两个与之相关…