一文搞懂到底什么是 AQS

news/2024/7/7 16:39:27/文章来源:https://www.cnblogs.com/fuxing/p/18284463

前言

日常开发中,我们经常使用锁或者其他同步器来控制并发,那么它们的基础框架是什么呢?如何实现的同步功能呢?本文将详细讲解构建锁和同步器的基础框架--AQS,并根据源码分析其原理。


一、什么是 AQS?

1. AQS 简介

AQS(Abstract Queued Synchronizer),抽象队列同步器,它是用来构建锁或其他同步器的基础框架。虽然大多数程序员可能永远不会使用到它,但是知道 AQS 的原理有助于理解一些锁或同步器的是如何运行的。

那么有哪些同步器是基于 AQS 实现的呢?这里仅是简单介绍,详情后续会单独总结一篇文章。

同步器 说明
CountDownLatch 递减的计数器,直至所有线程的任务都执行完毕,才继续执行后续任务。
Semaphore 信号量,控制同时访问某个资源的数量。
CyclicBarrier 递增的计数器,所有线程达到屏障时,才会继续执行后续任务。
ReentrantLock 防止多个线程同时访问共享资源,类似 synchronized 关键字。
ReentrantReadWriteLock 维护了读锁和写锁,读锁允许多线程访问,读锁阻塞所有线程。
Condition 提供类似 Object 监视器的方法,于 Lock 配合可以实现等待/通知模式。
FutureTask 当一个线程需要等待另一线程把某个任务执行完后才能继续执行,此时可以使用 FutureTask

如果你理解了 AQS 的原理,也可以基于它去自定义一个同步组件,下文会介绍。

2. AQS 数据结构

AQS 核心是通过对同步状态的管理,来完成线程同步,底层是依赖一个双端队列来完成同步状态的管理

  • 当前线程获取同步状态失败后,会构造成一个 Node 节点并加入队列末尾,同实阻塞线程。
  • 当同步状态释放时,会把头节点中的线程唤醒,让其再次尝试获取同步状态

如下图,这里只是简单绘制,具体流程见下面原理分析:

image.png

这里的每个 Node 节点都存储着当前线程、等待信息等。

3. 资源共享模式

我们在获取共享资源时,有两种模式:

模式 说明 示例
独占模式 Exclusive,资源同一时刻只能被一个线程获取 ReentrantLock
共享模式 Share,资源可同时被多个线程获取 Semaphore、CountDownLatch

二、AQS 原理分析

先简单说下原理分析的流程:

  1. 同步状态相关源码;
  2. 须重写的方法;
  3. Node 节点结构分析;
  4. 独占模式下的同步状态的获取与释放;
  5. 共享模式下的同步状态的获取与释放;

1. 同步状态相关

上面介绍到, AQS 核心是通过对同步状态的管理,来完成线程同步,所以首先介绍管理同步状态的三个方法,在自定义同步组件时,需要通过它们获取和修改同步状态。

//保证可见性
private volatile int state//获取当前同步状态。
protected final int getState() {return state;
}//设置当前同步状态。
protected final void setState(int newState) {state = newState;
}//使用 CAS 设置当前状态,保证原子性。
protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

2. 须重写的方法

AQS 是基于模板方法模式的,通过第一个 abstract 也可知道,AQS 是个抽象类,使用者需要继承 AQS 并重写指定方法。

以下这些方式是没有具体实现的,需要在使用 AQS 时在子类中去实现具体方法,等到介绍一些同步组件时,会详细说明如何重写。

//独占式获取同步状态,实现该方法须查询并判断当前状态是否符合预期,然后再进行CAS设置状态。
protected boolean tryAcquire (int arg) //独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态。
protected boolean tryRelease (int arg) //共享式获取同步状态,返回大于0的值表示获取成功,反之获取失败。
protected int tryAcquireShared (int arg)//共享式释放同步状态。
protected boolean tryReleaseShared (int arg)//当前同步器是否再独占模式下被线程占用,一般用来表示是否被当前线程独占。
protected boolean isHeldExclusively ()

3. Node 源码

Node 是双端队列中的节点,是数据结构的重要部分,线程相关的信息都存在每一个 Node 中。

3.1 Node 结构源码

源码如下:

static final class Node {//标记当前节点的线程在共享模式下等待。static final Node SHARED = new Node();//标记当前节点的线程在独占模式下等待。static final Node EXCLUSIVE = null;//waitStatus的值,表示当前节点的线程已取消(等待超时或被中断)static final int CANCELLED =  1;//waitStatus的值,表示后继节点的线程需要被唤醒static final int SIGNAL    = -1;//waitStatus的值,表示当前节点在等待某个条件,正处于condition等待队列中static final int CONDITION = -2;//waitStatus的值,表示在当前有资源可用,能够执行后续的acquireShared操作static final int PROPAGATE = -3;//等待状态,值如上,1、-1、-2、-3。volatile int waitStatus;//前趋节点volatile Node prev;//后继节点volatile Node next;//当前线程volatile Thread thread;//等待队列中的后继节点,共享模式下值为SHARED常量Node nextWaiter;//判断共享模式的方法final boolean isShared() {return nextWaiter == SHARED;}//返回前趋节点,没有报NPEfinal Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}//下面是三个构造方法Node() {}    // Used to establish initial head or SHARED markeNode(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}

3.2 设置头尾节点

Unsafe 类中,提供了一个基于 CAS 的设置头尾节点的方法,AQS 调用该方法进行设置头尾节点,保证并发编程中的线程安全。

//CAS自旋设置头节点
private final boolean compareAndSetHead(Node update) {return unsafe.compareAndSwapObject(this, headOffset, null, update);
}//CAS自旋设置尾节点,expect为当前线程“认为”的尾节点,update为当前节点
private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

4. 独占模式

资源同一时刻只能被一个线程获取,如 ReentrantLock。

4.1 获取同步状态

代码如下,调用 acquire 方法可以获取同步状态,底层就是调用须重写方法中的 tryAcquire。如果获取失败则进入同步队列中,即使后续对线程进行终端操作,线程也不会从同步队列中移除。

public final void acquire(int arg) {//调用须重写方法中的tryAcquireif (!tryAcquire(arg) &&//失败则进入同步队列中acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}

获取失败会先调用 addWaiter 方法将当前线程封装成独占式模式的节点,添加到AQS的队列尾部,源码如下。

private Node addWaiter(Node mode) {//将当前线程封装成对应模式下的Node节点Node node = new Node(Thread.currentThread(), mode);Node pred = tail;//尾节点if (pred != null) {//双端队列需要两个指针指向node.prev = pred;//通过CAS方式if (compareAndSetTail(pred, node)) {//添加到队列尾部pred.next = node;return node;}}//等待队列中没有节点,或者添加队列尾部失败则调用end方法enq(node);return node;
}//Node节点通过CAS自旋的方式被添加到队列尾部,直到添加成功为止。
private Node enq(final Node node) {//死循环,类似 while(1)for (;;) {Node t = tail;if (t == null) { // 须要初始化,代表队列的第一个元素if (compareAndSetHead(new Node()))//头节点就是尾节点tail = head;} else {//双端队列需要两个指针指向node.prev = t;//通过自旋放入队列尾部if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}

此时,通过 addWaiter 已经将当前线程封装成独占模式的 Node 节点,并成功放入队列尾部。接下来会调用acquireQueued 方法在等待队列中排队。

final boolean acquireQueued(final Node node, int arg) {//获取资源失败标识boolean failed = true;try {//线程是否被中断标识boolean interrupted = false;//死循环,类似 while(1)for (;;) {//获取当前节点的前趋节点final Node p = node.predecessor();//前趋节点是head,即队列的第二个节点,可以尝试获取资源if (p == head && tryAcquire(arg)) {//资源获取成功将当前节点设置为头节点setHead(node);p.next = null; // help GC,表示head节点出队列failed = false;return interrupted;}//判断当前线程是否可以进入waitting状态,详解见下方if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())	//阻塞当前线程,详解见下方interrupted = true;}} finally {if (failed)//取消获取同步状态,源码见下方的取消获取同步状态章节cancelAcquire(node);}
}//将当前节点设置为头节点
private void setHead(Node node) {head = node;node.thread = null;node.prev = null;
}//判断当前线程是否可以进入waitting状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//获取前趋节点的等待状态,含义见上方Node结构源码int ws = pred.waitStatus;if (ws == Node.SIGNAL)	//表示当前节点的线程需要被唤醒return true;if (ws > 0) {	//表示当前节点的线程被取消//则当前节点一直向前移动,直到找到一个waitStatus状态小于或等于0的节点do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);//排在这个节点的后面pred.next = node;} else {//通过CAS设置等待状态compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}//阻塞当前线程
private final boolean parkAndCheckInterrupt() {//底层调用的UnSafe类的方法 park:阻塞当前线程, unpark:使给定的线程停止阻塞LockSupport.park(this);//中断线程return Thread.interrupted();
}

acquireQueued 方法中,只有当前驱节点等于 head 节点时,才能够尝试获取同步状态,这时为什么呢?

因为 head 节点是占有资源的节点,它释放后才会唤醒它的后继节点,所以需要检测。还有一个原因是因为如果遇到了非 head 节点的其他节点出队或因中断而从等待中唤醒,这时种情况则需要判断前趋节点是否为 head 节点,是才允许获取同步状态。

获取同步状态的整体流程图如下:

image.png

4.2 释放同步状态

调用须重写方法中的 tryAcquire 进行同步状态的释放,成功则唤醒队列中最前面的线程,具体如下。

public final boolean release(int arg) {//调用须重写方法中的tryReleaseif (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)//唤醒后继节点的线程,详情见下方unparkSuccessor(h);return true;}return false;
}//唤醒后继节点的线程
private void unparkSuccessor(Node node) {//获取当前节点的等待状态int ws = node.waitStatus;if (ws < 0)//小于0则,则尝试CAS设为0compareAndSetWaitStatus(node, ws, 0);//获取后继节点Node s = node.next;//后继节点为空或者等待状态大于0,代表被节点被取消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)//底层调用的UnSafe类的方法 park:阻塞当前线程, unpark:使给定的线程停止阻塞LockSupport.unpark(s.thread);
}

4.3 其他情况的获取同步状态

除此之外,独占模式下 AQS 还提供了两个获取同步状态的方法,可中断的获取同步状态和超时获取同步状态。

acquire 方法获取锁失败的线程是不能被 interrupt 方法中断的,所以提供了另一个方法 ,从而让获取锁失败等待的线程可以被中断。底层源码与

public final void acquireInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())//中断则抛出异常throw new InterruptedException();if (!tryAcquire(arg))doAcquireInterruptibly(arg);
}

通过调用 tryAcquireNanos 可以在超时时间内获取同步状态,可以理解为是上述中断获取同步状态的增强版。


public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {if (Thread.interrupted())//中断则抛出异常throw new InterruptedException();return tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);
}

上面个两个方法的源码均与普通的独占获取同步状态的源码基本类似,感兴趣的话可以自行阅读,这里不做赘述。

5. 共享模式

资源可同时被多个线程获取,如 Semaphore、CountDownLatch。

5.1 获取同步状态

代码如下,调用 acquireShared 方法可以获取同步状态,底层就是先调用须重写方法中的 tryAcquireShared。

tryAcquireShared 返回值的含义:

  • 负数:表示获取资源失败
  • 0:表示获取资源成功,但是没有剩余资源
  • 正数:表示获取资源成功,还有剩余资源
public final void acquireShared(int arg) {//调用须重写方法中的tryAcquireSharedif (tryAcquireShared(arg) < 0)//获取资源失败,将当前线程放入队列的尾部并阻塞doAcquireShared(arg);
}

若获取资源失败,调用如下方法将当前线程放入队列的尾部并阻塞,直到有其他线程释放资源并唤醒当前线程。

//部分方法与独占模式下的方法公用,这里不再重复说明,详情见独占模式下的获取同步状态源码。
private void doAcquireShared(int arg) {//将当前线程封装成独占式模式的节点,添加到AQS的队列尾部,源码在独占模式中已分析。final Node node = addWaiter(Node.SHARED);//获取资源失败标识boolean failed = true;try {//线程被打断表示boolean interrupted = false;//死循环,类似 while(1)for (;;) {//获取当前节点的前趋节点final Node p = node.predecessor();//前趋节点是head,即队列的第二个节点,可以尝试获取资源if (p == head) {int r = tryAcquireShared(arg);if (r >= 0) {//将当前节点设置为头节点,若还有剩余资源,则继续唤醒队列中后面的线程。setHeadAndPropagate(node, r);p.next = null; // help GC 表示head节点出队列if (interrupted)selfInterrupt();failed = false;return;}}//判断当前线程是否可以进入waitting状态,源码在独占模式中已分析。if (shouldParkAfterFailedAcquire(p, node) &&//阻塞当前线程,源码在独占模式中已分析。parkAndCheckInterrupt()) interrupted = true;}} finally {if (failed)//取消获取同步状态,源码见下方的取消获取同步状态章节cancelAcquire(node);}
}/** propagate就是tryAcquireShared的返回值*	● 负数:表示获取资源失败*	● 0:表示获取资源成功,但是没有剩余资源*	● 正数:表示获取资源成功,还有剩余资源*/
private void setHeadAndPropagate(Node node, int propagate) {//将当前节点设置为头节点,源码在独占模式中已分析。Node h = head; //这时的h是旧的headsetHead(node);// propagate > 0:还有剩余资源// h == null 和 h = head) == null: 不会成立,因为addWaiter已执行// waitStatus < 0:若没有剩余资源,但waitStatus又小于0,表示可能有新资源释放// 括号中的 waitStatus < 0: 这里的 h 是此时的新的head(当前节点),if (propagate > 0 || h == null || h.waitStatus < 0 ||(h = head) == null || h.waitStatus < 0) {//获取当前节点的后继节点Node s = node.next;//后继节点不存在或者是共享锁都需要唤醒,可理解为只要后继节点不是独占模式,都要唤醒//可能会导致不必要的唤醒if (s == null || s.isShared())//唤醒操作在此方法中,详情见下方的释放源码doReleaseShared();}
}

5.2 释放同步状态

代码如下,调用 releaseShared 方法可以释放同步状态,底层就是先调用须重写方法中的 tryReleaseShared。

public final boolean releaseShared(int arg) {//调用须重写方法中的tryReleaseSharedif (tryReleaseShared(arg)) {//尝试释放资源成功,会继续唤醒队列中后面的线程。doReleaseShared();return true;}return false;
}//唤醒队列中后面的线程
private void doReleaseShared() {//死循环,自旋操作for (;;) {//获取头节点Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;//signal表示后继节点需要被唤醒if (ws == Node.SIGNAL) {//自旋将头节点的waitStatus状态设置为0if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))continue;            // loop to recheck cases//唤醒头节点的后继节点,源码见独占模式的释放unparkSuccessor(h);}//后继节点不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;                // loop on failed CAS}//判断头节点是否变化,没有则退出循环。//有变化说明其他线程已经获取了同步状态,需要进行重试操作。if (h == head)                   // loop if head changedbreak;}
}

6. 取消获取同步状态

无论是独占模式还是共享模式,所有的程获取同步状态的过程中,如果发生异常或是超时唤醒等,都需要将当前的节点出队,源码如下。

//一般在获取同步状态方法的finally块中
private void cancelAcquire(Node node) {if (node == null)return;node.thread = null;		//当前线程节点设为nullNode pred = node.prev;		//获取前驱节点//前趋节点为取消状态,向前遍历找到非取消状态的节点while (pred.waitStatus > 0)node.prev = pred = pred.prev;Node predNext = pred.next;	//获取非取消节点的下一个节点node.waitStatus = Node.CANCELLED;	//将当前节点的等待状态设为取消状态//当前节点是尾节点,则自旋将尾节点设置为前一个非取消节点if (node == tail && compareAndSetTail(node, pred)) {//将尾节点设为前一个非取消的节点,并将其后继节点设为null,help GCcompareAndSetNext(pred, predNext, null);} else {int ws;//用于表示等待状态//pred != head:前一个非取消的节点非头节点也非尾节点//ws == Node.SIGNAL:当前等待状态为待唤醒//若不是待唤醒则CAS设置为待唤醒状态//前一个非取消的节点的线程不为nullif (pred != head &&((ws = pred.waitStatus) == Node.SIGNAL ||(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&pred.thread != null) {//符合所有条件后,获取当前节点的后继节点Node next = node.next;if (next != null && next.waitStatus <= 0)//前一个非取消的节点的后继节点设为当前节点的后继节点//这样当前节点以及之前的已取消节点都会被移除compareAndSetNext(pred, predNext, next);} else {//前一个非取消的节点为头节点//唤醒后继节点的线程,详情见独占模式释放同步状态的源码//唤醒是为了执行shouldParkAfterFailedAcquire()方法,详解见上面的acquireQueued源码//该方法中从后往前遍历找到第一个非取消的节点并将中间的移除队列unparkSuccessor(node);}//移除当前节点node.next = node; // help GC}
}

三、总结

AQS 是用来构建锁或其他同步器的基础框架,底层是一个双端队列。支持独占和共享两种模式下的资源获取与释放,基于 AQS 可以自定义不同类型的同步组件。

在独占模式下,获取同步状态时,AQS 维护了一个双端队列,获取失败的线程都会被加入到队列中进行自旋,移出队列的条件就是前趋节点为 head 节点并成功获取同步状态。释放同步状态时,会唤醒 head 节点的后继节点。

在共享模式下,获取同步状态时,同样维护了一个双端队列,获取失败的的线程也会加入到队列中进行自旋,移除队列的条件也与独占模式一样。

但是在唤醒操作上,在资源数量足够的情况下,共享模式会将唤醒事件传递到后面的共享节点上,进行了后续节点的唤醒,解所成功后仍会唤醒后续节点。

关于 AQS 重要的几个组件的特点、原理以及对应的应用场景,后续会单独写一篇文章。若发现其他问题欢迎指正交流。


参考:

[1] 翟陆续/薛宾田. Java 并发编程之美.

[2] 方腾飞/魏鹏/程晓明. Java 并发编程的艺术.

[3] Lev Vygotsky. Java 并发编程实践

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flutter状态管理 provider使用

provider是flutter官方推荐的状态管理插件,是基于InheritedWidget实现的。 下面我们来讲一个provider的使用方法。 1.在pubspec.yaml文件中添加 provider: ^6.1.2 开发文档:https://pub-web.flutter-io.cn/packages/provider 可以查看使用方法和最新版本号。 添加完成后…

企业数字化转型:顶层规划方法

随着数字化时代的到来,发生了以数字化、智能化为典型特征的新一轮科技革命,各行各业利用互联网、大数据、云计算、人工智能、区块链技术对传统产业进行全方位、全链条改造,实施“上云用数赋智”行动,全面推进各行业数字化转型。数字经济的大门已然开启,数字经济顶层战略规…

Nuxt3 的生命周期和钩子函数(十)

摘要:本文详细介绍了Nuxt3框架中的五个webpack钩子函数:webpack:configResolved用于在webpack配置解析后读取和修改配置;webpack:compile在编译开始前调用,可修改编译选项;webpack:compiled在编译完成后调用,可处理编译结果;webpack:change在开发模式下文件变化时触发,…

Jenkins汉化

1、Jenkins版本:版本2.426.3) Manage Jenkins->选择Plugins->切换到Availabled plugin->搜索local,然后选中安装,如下图所示 2、安装完成后重启Jenkins,汉化完成。如下图所示 像个小学生一样努力学习

模拟集成电路设计系列博客——9.1 比较器

模拟集成电路设计 9.1 比较器 比较器可能是继放大器之后第二常用的电路元件,比较器用于判断一个信号是否大于或小于零,或者比较一个信号是否大于另一个。如我们之前的章节所见,比较器在ADC中非常常用。在其他的应用中也经常出现比较器,例如数据传输,开关电源稳压器等等。 …

prufer序列

prufer序列用途: 将带标号的树用唯一的整数序列表示出来,证明凯莱公式。构造方法:每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点。重复\(n-2\)次后就只剩下两个结点,算法结束。 举个栗子(本图来自baoziwu2,侵删)显然可以有一个用堆做的方法,…

【冷启动#2】实用的springboot tutorial入门demo

跟着官方文档熟悉一遍创建spring工程的步骤 https://spring.io/guides/gs/spring-boot https://juejin.cn/post/7077958723829760008 demo简介 整个demo的预期目标是: 管理一堆玩家的数据,数据库使用的是现成的我们虚拟机上安装的MySQL 项目结构参考 主要工作:创建并熟悉spr…

CentOS8部署zerotier异地组网

本文介绍了在CentOS 8系统上部署ZeroTier以实现异地组网的方法,包括安装、配置、加入网络、开启IP转发和设置路由表等步骤,并提出进一步实现公网无缝组网的展望。CentOS8部署zerotier异地组网 CentOS8部署zerotier异地组网 一、前言 本文在此前研究部署FRP进行内网穿透,但FR…

《DNK210使用指南 -CanMV版 V1.0》第九章 打印输出实验

第九章 打印输出实验 1)实验平台:正点原子DNK210开发板 2)章节摘自【正点原子】DNK210使用指南 - CanMV版 V1.0 3)购买链接:https://detail.tmall.com/item.htm?&id=782801398750 4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/k210/ATK-DN…

分享一款可编辑本地电脑文件的在线编辑器

之前见过在线版的VSCode,被惊讶到了。网页上竟然可以编辑电脑本地的文件,打破了网页无法编辑本地电脑文件的限制。一直好奇怎么做的。抽空研究了一下,然后发现其实也不难。背景 之前见过在线版的VSCode,被惊讶到了。网页上竟然可以编辑电脑本地的文件,打破了网页无法编辑本…

xhcms

xhcms 目录结构 admin --管理后台文件夹 css --存放css的文件夹 files --存放页面的文件夹 images --存放图片的文件夹 inc --存放网站配置文件的文件夹 install --网站进行安装的文件夹 seacmseditor --编辑器文件夹 templ…

lrzsz安装完成后rz乱码

rz选择文件夹后乱码传输不到linux里面, 这时候只需要rz -be再选择文件 这样就可以进行后续的解压了tar -zxvf jdk-8u65-linux-x64.tar.gz

椭流线法设计配光器

本文介绍了利用椭流线法设计高效均匀的LED配光器,通过对边光原理、反射定律及椭圆几何特性的深入分析,结合Matlab和SolidWorks软件实现光学仿真,最终成功设计出接收效率高、均匀度优的配光器。椭流线法设计配光器 椭流线法设计配光器 一、设计原理 1、边光原理 边光原理是非…

wx云开发增删改查

首先是.wxml文件,此处为固定数据的新增<button type="primary" bind:tap="addData"> //调用.js中addData方法插入数据 </button>对应.js文件//添加数据addData(){wx.showLoading({ //showLoading(api接口)(防止用户多次…

剪裁法设计配光器

剪裁法设计配光器通过光源角分割和目标面分割,利用边光原理和反射定律,计算并构建光学母线,以实现高均匀度和高光效的均匀圆斑光学设计。剪裁法设计配光器 剪裁法设计配光器 一、设计原理边光原理 边光原理是非成像光学中的一个基础原理,其内容可以表述为:来自光源边缘的光…

iOS-列表视图

在iOS开发中,UITableView和UICollectionView是两个非常核心的用于展示集合数据的UI组件。它们都能以列表的形式展示数据,但各自的特点和使用场景有所不同。 UITableView UITableView用于展示和管理垂直滚动的单列数据列表。它是以行的形式展示数据,每行(cell)可以展示相同…

CPC配光系统设计

本文详细介绍了CPC(复合抛物线聚光器)配光系统的设计过程,包括设计原理、抛物流线几何特性及其设计要求和流程,并通过Matlab和SolidWorks绘制模型,最后在TracePro中进行仿真验证,确保系统满足均匀照度和高效接收率的目标。CPC配光系统设计 CPC配光系统设计 一、设计原理 …

VMware vSphere Tanzu部署_14_部署容器应用

1.部署运行容器应用 1.1. 登录tkc集群 jianhua@napp:~/tkc$ kubectl vsphere login --server=192.168.203.194 \ --tanzu-kubernetes-cluster-name tkc-dev-cluster \ --tanzu-kubernetes-cluster-namespace tkc-01 \ --vsphere-username administrator@vsphere.local \ --ins…

贝塞尔曲线原理、推导及Matlab实现

本文详细解析了贝塞尔曲线的定义、性质、构建方法以及多种阶数的推导公式,并提供了完整的Matlab代码用于绘制和计算贝塞尔曲线。贝塞尔曲线原理、推导及Matlab实现 贝塞尔曲线原理、推导及Matlab实现 一、简介 贝塞尔曲线提出 在数学的数值分析领域中,贝塞尔曲线(English:B…

刘积仁的大健康“长跑”

软件是一个长命的产业,但软件企业的寿命都很短。懂得怕死,才能有机会活得长久。 这一次,刘积仁又为东软医疗找到了强大助力!中国通用技术(集团)控股有限责任公司(以下简称通用技术集团)所属资本公司战略投资东软集团在医疗健康领域资的创新业务公司——东软医疗,双方也由…