Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析

news2024/12/28 20:40:07

Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析

文章目录

  • Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析
    • 一、AQS的核心思想
    • 二、AQS中关键的内部结构
      • 一、Node内部类
      • 二、CLH队列
      • 三、同步状态 state
      • 四、Condition条件队列
    • 三、AQS同步机制的实现源码解析
      • 一、AQS获取独占锁的实现
        • acquire方法
        • addWaiter方法
        • acquireQueued方法
      • 二、AQS释放独占锁的实现
      • 三、AQS获取共享锁的实现
      • 四、AQS释放共享锁的实现
    • 总结

AQS (AbstractQueuedSynchronizer,抽象队列同步器) 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask 等等皆是基于 AQS 的。可见,AQS是我们理解其他Java JUC并发容器的基础。

一、AQS的核心思想

AQS 核心思想是为了解决多线程环境下对共享资源的请求同步问题。对于请求的共享资源,如果资源存在空闲,则当前请求获取到对应资源正常执行,否则,会转换为阻塞状态,直到有资源被释放后,由工作线程来唤醒。

AQS借助CLH队列锁(由于是 Craig、Landin 和 Hagersten三位大佬的发明,因此命名为CLH锁。)来实现上述的线程阻塞等待功能。这里CLH锁是一种自旋锁,当多线程竞争一把锁时,获取不到锁的线程,会排队进入CLH队列的队尾,然后自旋等待,直到其前驱线程释放锁。CLH队列锁的好处是唤醒线程时避免了惊群效应,因为只会唤醒当前线程的下一个等待线程。

除了CLH队列外,AQS中还有一个很重要的队列:Condition条件队列Condition声明了一组等待/通知的方法,这些方法的功能与Object中的wait/notify/notifyAll等方法相似,只不过Condition 中的方法则要配合锁对象使用,并通过newCondition方法获取实现类对象。注意Condition对象只能在独占锁中才能使用。

下图给出了AQS组件的结构框架图:

在这里插入图片描述

AQS对资源的共享方式包括两种:

  • Exclusive(独占):只允许一个线程能执行,且忽略中断,比如ReentrantLock。又可以分为公平锁和非公平锁
    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
    • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
  • Share(共享):非排他性锁。多个线程可同时向共享资源申请资源,直到没有可用资源时,对应线程才阻塞,如 Semaphore/CountDownLatch。SemaphoreCountDownLatCh、 CyclicBarrier、ReadWriteLock 等。

二、AQS中关键的内部结构

一、Node内部类

AQS内部维护着一个FIFO的队列,该队列就是用来实现线程的并发访问控制。队列中的元素是一个Node类型的节点,Node的主要属性如下:

static final class Node {
    int waitStatus;
    Node prev;
    Node next;
    Node nextWaiter;
    Thread thread;
}

各属性表示的含义如下:

  • waitStatus:表示节点的状态,其中包含的状态有:
    • CANCELLED:值为1,表示当前节点已取消调度(当timeout或被中断,会触发变更为此状态)
    • SIGNAL:值为-1,表示当前节点的的后继节点将要或者已经被阻塞,在当前节点释放的时候需要unpark后继节点。后继节点入队时,会将前驱节点的状态更新为SIGNAL。
    • CONDITION:值为-2,表示当前节点在condition队列中等待,当其他线程调用了Condition的signal()方法后,CONDITION状态的节点将从Condition等待队列中转移到同步队列中,等待获取资源。
    • PROPAGATE:值为-3,表示releaseShared需要被传播给后续节点(仅在共享模式下使用)。即前驱节点不仅会唤醒其后驱节点,同时也可能会唤醒后驱的后驱节点,所以叫传播。
    • 0:无状态,表示当前节点在队列中等待获取锁。
  • prev:CLH队列中的前继节点
  • next:CLH队列中的后继节点
  • nextWaiter:存储Condition条件队列的后继节点
  • thread:当前线程

其中,CLH队列里还有一个head节点和一个tail节点,分别表示头结点和尾节点,其中头结点是空节点,本身不存储Thread,仅保存next结点的引用。

二、CLH队列

CLH队列用来存储Node节点封装的线程阻塞队列。每个获取资源失败的线程,根据先来后到,利用CAS原子操作依次插入到CLH队列的尾部,等待被唤醒获取资源。为了便于尾节点插入数据,CLH队列做成了双端队列,即包含head和tail的FIFO队列。因此可以说,AQS实现多线程间对共享资源的同步访问,就是依靠CLH队列来实现的。

CLH队列每次unpark解除线程阻塞时,都会从队列的头节点来选择线程进行接触阻塞。

CLH每一个线程都是一个自旋锁,非常消耗CPU。等待锁的每个线程在自己的某个变量上自旋等待,这个变量指向自己的前驱节点中的变量,通过不断地自旋,感知到前驱节点的变化后成功获取到锁。

我们可以看到公平锁就是最初的实现理念就是CLH队列。

三、同步状态 state

在AQS中维护了一个同步状态变量state,用来表示同步状态。

不同线程获取和释放资源时,都会对state进行更新。比如state > 0表示可以获取共享资源,否则无法获取。该变量对不同的子类实现具有不同的意义。

比如,对ReentrantLock来说,它表示加锁的状态:

  • 无锁时state=0,有锁时state>0;
  • 第一次加锁时,将state设置为1;
  • 由于ReentrantLock是可重入锁,所以持有锁的线程可以多次加锁,经过判断加锁线程就是当前持有锁的线程时(即exclusiveOwnerThread==Thread.currentThread()),即可加锁,每次加锁都会将state的值+1,state等于几,就代表当前持有锁的线程加了几次锁;
  • 解锁时每解一次锁就会将state减1,state减到0后,锁就被释放掉,这时其它线程可以加锁;
  • 当持有锁的线程释放锁以后,如果是等待队列获取到了加锁权限,则会在等待队列头部取出第一个线程去获取锁,获取锁的线程会被移出队列;

其他

  • ReentrantReadWriteLock 的 state 高 16 位代表读锁状态,低 16 位代表写锁状态
  • Semaphore 的 state 用来表示可用信号的个数
  • CountDownLatch 的 state 用来表示计数器的值

state变量定义如下:

/**
 * The synchronization state.
 */
private volatile int state;

四、Condition条件队列

Object 的 wait、notify 函数是配合 Synchronized 锁实现线程间同步协作的功能,AQS 的 ConditionObject 条件变量也提供这样的功能,通过 ConditionObject 的 await 和 signal 两类函数完成。

ConditionObject是通过基于单链表的条件队列来管理等待线程的。线程在调用await方法进行等待时,会释放同步状态。同时线程将会被封装到一个等待节点中,并将节点置入条件队列尾部进行等待。当有线程在获取独占锁的情况下调用signal或singalAll方法时,队列中的等待线程将会被唤醒,重新竞争锁。另外,需要说明的是,一个锁对象可同时创建多个 ConditionObject 对象,这意味着多个竞争同一独占锁的线程可在不同的条件队列中进行等待。在唤醒时,可唤醒指定条件队列中的线程。

不同于 Synchronized 锁,一个 AQS 可以对应多个条件变量,而 Synchronized 只有一个。

在这里插入图片描述

如上图所示,AQS中维护这两个队列:CLH队列和Condititon条件队列。Condition条件队列时单向队列,利用上述提及的nextWaiter指针指向下一个节点。Condition队列只入队执行await的线程节点,并等待被调用singnal方法唤醒。从Condition队列中被唤醒的线程会入队到CLH队列。过程类似于Object类的wait/notify原理过程。

三、AQS同步机制的实现源码解析

AQS针对上述提及的资源共享的两种不同模式,利用不同的方式进行实现:

独占式

  • acquire():获取资源
  • release():释放资源

共享式

  • acquireShard:共享模式下获取资源
  • releaseShard:共享模式下释放资源

一、AQS获取独占锁的实现

acquire方法

acquire是AQS中的方法,代码如下:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

该方法主要工作如下:

  • 执行 tryAcquire 函数,tryAcquire 是由子类实现,代表获取资源是否成功,如果资源获取失败,执行下面的逻辑
  • 执行 addWaiter 函数(前面已经介绍过),根据当前线程创建出独占式节点,并入队 CLH 队列
  • 执行 acquireQueued 函数,自旋阻塞等待获取资源
  • 如果 acquireQueued 函数中获取资源成功,根据线程是否被中断状态,来决定执行线程中断逻辑

在这里插入图片描述

图中比较重要的几个方法:

addWaiter方法

看下addWaiter方法的定义:

private Node addWaiter(Node mode) {
    // 根据当前线程创建一个Node对象
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    // 判断tail是否为空,如果为空表示队列是空的,直接enq
    if (pred != null) {
        node.prev = pred;
        // 这里尝试CAS来设置队尾,如果成功则将当前节点设置为tail,否则enq
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

该方法核心逻辑就是将当前线程封装为一个Node,然后利用CAS操作添加到队列尾部。

acquireQueued方法

该方法的功能是循环的尝试获取锁,直到成功为止,最后返回中断标志位。

/**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        // 异常状态,默认是true
        boolean failed = true;
        try {
            // 表示当前线程是否被中断过,默认是否
            boolean interrupted = false;
            // 一直自旋
            for (;;) {
                // 获取node节点的前驱节点
                final Node p = node.predecessor();
                // 如果前驱节点是首节点,说明当前线程所在的节点是列表中第一个节点,则获取资源成功
                if (p == head && tryAcquire(arg)) {
                    // 获取资源成功,设置当前节点为头节点,清空当前节点的信息
                    setHead(node);
                    // 原来首节点的next置为null,这样该节点就变成一个孤立的节点,有利于下次GC时,比如使用标记清除法遍历时快速GC掉
                    p.next = null; // help GC
                    // 非异常状态,防止指向finally逻辑
                    failed = false;
                    // 返回线程中断状态
                    return interrupted;
                }
                /**
                 * 如果前驱节点不是首节点,先执行shouldParkAfterFailedAcquire函数,
                 * shouldParkAfterFailedAcquire做了三件事
                 * 1.如果前驱节点的等待状态是SIGNAL,返回true,执行parkAndCheckInterrupt函数,返回false
                 * 2.如果前驱节点的等大状态是CANCELLED,把CANCELLED节点全部移出队列(条件节点)
                 * 3.以上两者都不符合,更新前驱节点的等待状态为SIGNAL,返回false
                 */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //使用LockSupport类的静态方法park挂起当前线程,直到被唤醒,
                    // 唤醒后检查当前线程是否被中断,返回该线程中断状态并重置中断状态
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 尝试获取资源失败并执行异常,取消请求,将当前节点从队列中移除
            if (failed)
                cancelAcquire(node);
        }
    }

整体流程如下图所示:

在这里插入图片描述

二、AQS释放独占锁的实现

AQS使用release()方法来对资源进行释放,release方法中,唤醒线程时会首先唤醒CLH队列中的队头节点所对应的线程,代码如下:

public final boolean release(int arg) {
    	// 释放资源成功,tryRelease子类实现
        if (tryRelease(arg)) {
            // 获取头部线程节点
            Node h = head;
            // 首节点不为null,并且等待状态不为0
            if (h != null && h.waitStatus != 0)
                // 唤醒CLH队列队首节点对应的线程(首节点的下一个节点)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

 private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
     	// 获取节点等待状态
        int ws = node.waitStatus;
        if (ws < 0)
            // CAS操作更新节点状态为0
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
     	// s指向队首第一个节点(不包括首节点)
        Node s = node.next;
     	// 如果队首节点状态为CANCELLED,则从队尾开始循环向前,直到获取到第一个正常节点为止
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            // 唤醒队首节点对应线程
            LockSupport.unpark(s.thread);
    }

释放流程如下所示:
在这里插入图片描述

三、AQS获取共享锁的实现

acquireShared 是个模板函数,模板流程就是线程获取共享资源,如果获取到资源,线程直接返回,否则进入 CLH 队列,直到获取到资源为止,且整个过程忽略中断的影响,acquireShared 函数代码如下:

    /**
     * Acquires in shared mode, ignoring interrupts.  Implemented by
     * first invoking at least once {@link #tryAcquireShared},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquireShared} until success.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquireShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     */
    public final void acquireShared(int arg) {
        /**
        	1. 负数表示失败
        	2. 0表示成功,但没有剩余可用资源
        	3. 正数表示成功且有剩余资源
        */
        // <0表示获取资源失败
        if (tryAcquireShared(arg) < 0)
            // 自旋阻塞等待获取资源
            doAcquireShared(arg);
    }

doAcquireShared 函数与独占式的 acquireQueued 函数逻辑基本一致,区别在于:

  • addWaiter方法:在共享锁的方式中,创建节点的方式是:final Node node = addWaiter(Node.SHARED);这里,Node node = new Node(Thread.currentThread(), mode);表明添加了一个共享式节点,节点标记是共享式,并入队CLH队列。
  • setHeadAndPropagate(node, r)方法:设置自己尾队列头节点,并尝试唤醒后继节点。即获取资源成功时,还会尝试唤醒后继资源,因为资源数可能>0,代表还有资源可获取,所以需要做后续线程节点的唤醒。

四、AQS释放共享锁的实现

同样的,AQS中提供了releaseShared方法来释放共享资源,唤醒CLH队列的头节点的下一个节点,也就是CLH队列中第一个等待线程所对应的节点。

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {//释放资源成功,tryReleaseShared子类实现
            //唤醒后继节点
            doReleaseShared();
            return true;
        }
        return false;
    }
    
private void doReleaseShared() {
        for (;;) {
            //获取头节点
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
    
                if (ws == Node.SIGNAL) {//如果头节点等待状态为SIGNAL
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//更新头节点等待状态为0
                        continue;            // loop to recheck cases
                    //唤醒头节点下个线程节点
                    unparkSuccessor(h);
                }
                //如果后继节点暂时不需要被唤醒,更新头节点等待状态为PROPAGATE
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;               
            }
            if (h == head)              
                break;
        }
    }

与独占式释放资源区别不大,都是唤醒头节点的下个节点,就不做过多描述了。

总结

本文从独占锁的实现出发,比较完整的分析了AQS内部独占锁和共享锁的实现。总体来说实现的思路很清晰,就是使用了标志位+队列的方式来处理锁的状态,包括锁的获取,锁的竞争以及锁的释放。在AQS中,利用CLH队列和Condition队列来维护多线程队共享资源的同步访问,state可以表示锁的数量,也可以表示其他状态,state的含义由子类去定义,自己只是提供了对state的维护。AQS通过state来实现线程对资源的访问控制,而state具体的含义要在子类中定义。

对于 AbstractQueuedSynchronizer 的分析,最核心的就是 sync queue 的分析。

  • 每一个结点都是由前一个结点唤醒
  • 当结点发现前驱结点是 head 并且尝试获取成功,则会轮到该线程运行。
  • condition queue 中的结点向 sync queue 中转移是通过 signal 操作完成的。
  • 当结点的状态为 SIGNAL 时,表示后面的结点需要运行。

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

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

相关文章

houdini 之copy to points

将第一个输入中的几何图形复制到第二个输入的点上。 属性备注Source Group几何体来源Target Points要复制到的目标点集合Show Guide Geometry是否显示该操作预览流程Pack and Instance在复制之前将输入几何体打包到嵌入式打包图元中。这导致输入几何被每个副本共享&#xff08;…

跟着实例学Go语言(一)

本教程全面涵盖了Go语言基础的各个方面。一共80个例子&#xff0c;每个例子对应一个语言特性点&#xff0c;非常适合新人快速上手。 教程代码示例来自go by example&#xff0c;文字部分来自本人自己的理解。 本文是教程系列的第一部分&#xff0c;共计20个例子、约1万字。 目…

电子学会2021年3月青少年软件编程(图形化)等级考试试卷(四级)答案解析

目录 一、单选题&#xff08;共15题&#xff0c;每题2分&#xff0c;共30分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题&#xff08;共4题&#xff0c;共50分&#xff09; 青少年软件编程&#xff08;图形化&a…

python与pycharm配置http服务

下载安装pycharm 下载pycharm 提取码&#xff1a;slgh 在任意自己工作的目录下创建两个文件夹&#xff0c;www文件夹及其目录下cgi-bin文件夹 自己的工作目录\www\cgi-bin 打开pycharm创建工程&#xff0c;选择www\cgi-bin目录 配置cgi&#xff0c;选择Run菜单&#xff0c;…

动漫制作技巧如何制作动漫视频

动漫制作技巧是很多新人想了解的问题&#xff0c;今天小编就来解答与大家分享一下动漫制作流程&#xff0c;为了帮助有兴趣的同学理解&#xff0c;大多数人会选择动漫培训机构&#xff0c;那么今天小编就带大家来看看动漫制作要掌握哪些技巧&#xff1f; 一、动漫作品首先完成…

MedNeRF:用于从单个X射线重建3D感知CT投影的医学神经辐射场

摘要 计算机断层扫描&#xff08;CT&#xff09;是一种有效的医学成像方式&#xff0c;广泛应用于临床医学领域&#xff0c;用于各种病理的诊断。多探测器CT成像技术的进步实现了额外的功能&#xff0c;包括生成薄层多平面横截面身体成像和3D重建。然而&#xff0c;这涉及患者暴…

R语言确定聚类的最佳簇数:3种聚类优化方法

确定数据集中最佳的簇数是分区聚类&#xff08;例如k均值聚类&#xff09;中的一个基本问题&#xff0c;它要求用户指定要生成的簇数k。 最近我们被客户要求撰写关于聚类的研究报告&#xff0c;包括一些图形和统计输出。 一个简单且流行的解决方案包括检查使用分层聚类生成的树…

基于正则化Regularized Interpolation插值算法的图像超分辨重构研究-附Matlab代码

⭕⭕ 目 录 ⭕⭕✳️ 一、引言✳️ 二、图像复原基本原理✳️ 三、正则化插值原理✳️ 四、实验验证✳️ 五、参考文献✳️ 六、Matlab程序获取与验证✳️ 一、引言 图像是一种表达信息的形式&#xff0c;其中&#xff0c;数字图像反馈的信息更加丰富。 在获取图像的过程中&am…

【Redis】Redis安装步骤和特性以及支持的10种数据类型(Redis专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

【20221204】【每日一题】监控二叉树

给定一个二叉树&#xff0c;我们在树的节点上安装摄像头。 节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。 计算监控树的所有节点所需的最小摄像头数量。 思路&#xff1a; 1、要尽可能的少安装摄像头&#xff0c;那么摄像头不可能安装在叶子节点上&#xff0c…

TLS及CA证书申请流程

一、概述 SSL 是“Secure Sockets Layer”的缩写&#xff0c;中文叫做“安全套接层”。它是在上世纪90年代中期&#xff0c;由网景公司设计的。 SSL/TLS是同一种协议&#xff0c;只不过是在不同阶段的不同称呼。 SSL协议位于TCP/IP协议与各种应用层协议之间&#xff0c;为数据通…

基于事件驱动的微服务教程

基于事件驱动的微服务教程 使用 Spring Boot、Spring Cloud、Kafka 和 Elasticsearch 掌握具有模式的事件驱动微服务架构 课程英文名&#xff1a;Event-Driven Microservices Spring Boot, Kafka and Elastic 此视频教程共22.0小时&#xff0c;中英双语字幕&#xff0c;画质…

javaee之黑马旅游网1

这是一个用来锻炼javaweb基础知识的项目&#xff0c;先来导入一些我们准备好的文件 下面这些东西是我们项目必备的&#xff0c;我们提前准备好了 &#xff0c;这个我会上传到我的资源&#xff0c;你们可以自己去下载 利用maven来创建一个项目 选择无骨架创建项目&#xff0c;域…

[附源码]计算机毕业设计小型银行管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

JavaWeb_第6章_FilterListenerAjax

JavaWeb_第6章_Filter&Listener&Ajax 文章目录JavaWeb_第6章_Filter&Listener&Ajax1&#xff0c;Filter1.1 Filter概述1.2 Filter快速入门1.2.1 开发步骤1.2.2 代码演示1.3 Filter执行流程1.4 Filter拦截路径配置1.5 过滤器链1.5.1 概述1.5.2 代码演示1.5.3 问…

最新版本zookeeper+dubbo-admin

zookeeper 下载地址 :https://archive.apache.org/dist/zookeeper/ 修改conf下zoo_sample.cfg - >zoo.cfgbin下启动zkServer.cmd启动成功 :binding to port 0.0.0.0/0.0.0.0:2181 问题1&#xff1a;zookeper安装 1.去官网下载apache-zookeeper-3.6.2-bin.tar.gz名字中带有…

通用的改进遗传算法求解带约束的优化问题(MATLAB代码)

目录 1 概述 2 遗传算法 2.1 遗传算法的基本概念 2.2 遗传算法的特点 2.3 程序框图 3 运行结果 4 通用的改进遗传算法求解带约束的优化问题&#xff08;MATLAB代码&#xff09; 1 概述 遗传算法(Genetic Algorithm,GA)是模拟生物在自然环境中的遗传和进化过程而形成的自…

Spark中宽依赖、窄依赖、Job执行流程

一、宽依赖和窄依赖的定义 【宽依赖&#xff1a;】 宽依赖表示同一个父&#xff08;上游&#xff09;RDD 的 Partition 被多个子&#xff08;下游&#xff09;RDD 的 Partition 依赖&#xff0c;会引起 Shuffle&#xff0c;总结&#xff1a;宽依赖我们形象的比喻为多生。有shu…

DPD(Digital Pre-Distortion,数字预失真)

什么是DPD 下图中图A是一个理想PA的输入输出关系&#xff0c;它具有线性特性&#xff0c;也就是说输出信号的功率与输入信号功率具有线性关系。但是&#xff0c;现实中使用的PA却不具备理想PA的线性特性。如图C所示&#xff0c;现实PA的输出与输入是非线性关系。为了让非理想P…

HCIA 笔记(1)

一、什么是计算机网络&#xff1a; 二、什么是云技术&#xff1f; 云技术 包含 云存储&#xff08;百度网盘&#xff09; 、云计算&#xff08;分布式计算&#xff09; 三、计算机技术是怎么实现的&#xff1f; 答&#xff1a;抽象语言&#xff08;高级语言、汇编语言等&…