什么是CAS和AQS

article/2025/9/29 14:45:49

什么是CAS和AQS

  • CAS理解
    • 概念
    • ABA 问题
  • AQS理解
    • 获取同步状态
    • AQS源码分析
      • 获取锁
      • 释放锁

CAS是一种乐观锁,通过在修改数据时,通过和原来的值进行比较,判断是否有被其他人改变。然后AQS是有一个对列,当线程获取不到锁的时候,就进入这个等待队列中,平常是一种公平锁的实现。但是具体是如何理解呢,作为一个程序猿,我不能只懂得搬砖呀,所以还是要去理解下的。

CAS理解

概念

cas,比较并交换。cas算法的过程是这样的,cas包括有三个值:
v表示要更新的变量
e表示预期值,就是旧的值
n表示新值
更新时,判断只有e的值等于v变量的当前旧值时,才会将n新值赋给v,更新为新值。
否则,则认为已经有其他线程更新过了,则当前线程什么都不操作,最后cas放回当前v变量的真实值。
CAS是一种乐观锁,它抱着乐观的态度认为自己一定可以成果。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理, CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

原子包java.util.concurrent.atomic

这个包里面提供了一组原子类。其基本的特性就 是在多线程环境下,当有多个线程同时执行这些类的实例包含 的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像 自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的 理解。 相对于synchronized这种阻塞算法,CAS是非阻塞算法的一种常见实现。由于一般CPU切换时间比CPU 指令集操作更加长,所以这些原子类在性能上有了很大的提升。如下代码:

public class AtomicInteger extends Number implements java.io.Serializable {private volatile int value;public final int get() {return value;}public final int getAndlncrement。{for (;;) { //CAS自旋,一直尝试,直达成功int current = get。;int next = current + 1;if (compareAndSet(current, next))return current;}}public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwaplnt(this, valueOffset, expect, update);}
} 

getAndIncrement采用了CAS操作,每次从内存中读取数据然后将此数据和+ 1后的结果进行 CAS操作, 如果成功就返回结果,否则重试直到成功为止。

ABA 问题

CAS会导致“ABA问题”。CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时 刻比较并替换,那么在这个时间差类会导致数据的变化。

比如:

一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,
并且 two进行了一些操作变成了 B,然后two又将V位置的数据变成A,
这时候线程one进行CAS操 作发现内存中仍然是A,然后one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

部分乐观锁的实现是通过版本号(version)的方式来解决ABA问题,乐观锁每次在执行数据的修 改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本 号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问 题,因为版本号只会增加不会减少。

AQS理解

AQS,即AbstractQueuedSynchronizer, 队列同步器,它是Java并发用来构建锁和其他同步组件的基础框架。
AQS是一个抽象类,主是是以继承的方式使用。AQS本身是没有实现任何同步接口的,它仅仅只是定义了同步状态的获取和释放的方法来供自定义的同步组件的使用。一般是同步组件的静态内部类,即通过组合的方式使用。
抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch

在这里插入图片描述
它维护了一个volatile int state(代表共享资源)和一个FIFO(双向队列)线程等待队列(多线程争用资源被阻塞时会进入此队列)

state的访问方式有三种:

getState()
setState()
compareAndSetState()

同步状态就是这个int型的变量state. head和tail分别是同步队列的头结点和尾结点。假设state=0表示同步状态可用(如果用于锁,则表示锁可用),state=1表示同步状态已被占用(锁被占用)。

下面举例说下获取和释放同步状态的过程:

获取同步状态

假设线程A要获取同步状态(这里想象成锁,方便理解),初始状态下state=0,所以线程A可以顺利获取锁,
A获取锁后将state置为1。在A没有释放锁期间,线程B也来获取锁,此时因为state=1,表示锁被占用,
所以将B的线程信息和等待状态等信息构成出一个Node节点对象,放入同步队列,head和tail分别指向队列的
头部和尾部(此时队列中有一个空的Node节点作为头点,head指向这个空节点,空Node的后继节点是B对应的
Node节点,tail指向它),同时阻塞线程B(这里的阻塞使用的是LockSupport.park()方法)。
后续如果再有线程要获取锁,都会加入队列尾部并阻塞。
释放同步状态

当线程A释放锁时,即将state置为0,此时A会唤醒头节点的后继节点(所谓唤醒,其实是调用
LockSupport.unpark(B)方法),即B线程从LockSupport.park()方法返回,此时B发现state已经为0,
所以B线程可以顺利获取锁,B获取锁后B的Node节点随之出队。
上面只是简单介绍了AQS获取和释放的大致过程,下面结合AQS和ReentrantLock源码来具体看下JDK是如何实现的,特别要注意JDK是如何保证同步和并发操作的。

AQS源码分析

接下来以ReentrantLock的源码入手来深入理解下AQS的实现。上面说过AQS一般是以继承的方式被使用,同步组件内部组合一个继承了AQS的子类。在ReentrantLock类中,有一个Sync成员变量,即是继承了AQS的子类,源码如下:

public class ReentrantLock implements Lock, java.io.Serializable {private static final long serialVersionUID = 7373984872572414699L;/** Synchronizer providing all implementation mechanics */private final Sync sync;/*** Base of synchronization control for this lock. Subclassed* into fair and nonfair versions below. Uses AQS state to* represent the number of holds on the lock.*/abstract static class Sync extends AbstractQueuedSynchronizer {...}
}

这里的Sync也是一个抽象类,其实现类为FairSync和NonfairSync,分别对应公平锁和非公平锁。ReentrantLock的提供一个入参为boolean值的构造方法,来确定使用公平锁还是非公平锁:

     public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

获取锁

这里以NonfairSync类为例,看下它的Lock()的实现:

     final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}

lock方法先通过CAS尝试将同步状态(AQS的state属性)从0修改为1。若直接修改成功了,则将占用锁的线程设置为当前线程。看下compareAndSetState()和setExclusiveOwnerThread()实现:

     protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);}

可以看到compareAndSetState底层其实是调用的unsafe的CAS系列方法。

    protected final void setExclusiveOwnerThread(Thread thread) {exclusiveOwnerThread = thread;}

exclusiveOwnerThread属性是AQS从父类AbstractOwnableSynchronizer中继承的属性,用来保存当前占用同步状态的线程。

如果CAS操作未能成功,说明state已经不为0,此时继续acquire(1)操作,这个acquire()由AQS实现提供:

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}//简单解释下:
//tryAcquire方法尝试获取锁,如果成功就返回,如果不成功,则把当前线程和等待状态信息构适成一个Node
//节点,并将结点放入同步队列的尾部。然后为同步队列中的当前节点循环等待获取锁,直到成功。

首先看tryAcquire(arg)在NonfairSync中的实现(这里arg=1):

        protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}r

解释一下:
首先获取AQS的同步状态(state),在锁中就是锁的状态,如果状态为0,则尝试设置状态为arg(这里为1),
若设置成功则表示当前线程获取锁,返回true。这个操作外部方法lock()就做过一次,这里再做只是为了再
尝试一次,尽量以最简单的方式获取锁。

如果状态不为0,再判断当前线程是否是锁的owner(即当前线程在之前已经获取锁,这里又来获取),如果是
owner, 则尝试将状态值增加acquires,如果这个状态值越界,抛出异常;如果没有越界,则设置后返回true。这里可以看非公平锁的涵义,即获取锁并不会严格根据争用锁的先后顺序决定。这里的实现逻辑类似synchroized关键字的偏向锁的做法,即可重入而不用进一步进行锁的竞争,也解释了ReentrantLock中Reentrant的意义。

如果状态不为0,且当前线程不是owner,则返回false。
回到上面的代码,tryAcquire返回false,接着执行addWaiter(Node.EXCLUSIVE),这个方法创建结点并入队,来看下源码:

    private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}

首先创建一个Node对象,Node中包含了当前线程和Node模式(这时是排他模式)。tail是AQS的中表示同步队列队尾的属性,刚开始为null,所以进行enq(node)方法,从字面可以看出这是一个入队操作,来看下具体入队细节:

    private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}

方法体是一个死循环,本身没有锁,可以多个线程并发访问,假如某个线程进入方法,此时head, tail都为null, 进入if(t==null)区域,从方法名可以看出这里是用CAS的方式创建一个空的Node作为头结点,因为此时队列中只一个头结点,所以tail也指向它,第一次循环执行结束。注意这里使用CAS是防止多个线程并发执行到这儿时,只有一个线程能够执行成功,防止创建多个同步队列。

进行第二次循环时(或者是其他线程enq时),tail不为null,进入else区域。将当前线程的Node结点(简称CNode)的prev指向tail,然后使用CAS将tail指向CNode。看下这里的实现:

    private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);}

expect为t, t此时指向tail,所以可以CAS成功,将tail重新指向CNode。此时t为更新前的tail的值,即指向空的头结点,t.next=node,就将头结点的后续结点指向CNode,返回头结点。其他线程再插入节点以此类推,都是在追加到链表尾部,并且通过CAS操作保证线程安全。通过上面分析可知,AQS的写入是一种双向链表的插入操作,至此addWaiter分析完毕。addWaiter返回了插入的节点,作为acquireQueued方法的入参,看下源码:

    final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

可以看到,acquireQueued方法也是一个死循环,直到进入 if (p == head && tryAcquire(arg))条件方法块。还是接着刚才的操作来分析。acquireQueued接收的参数是addWaiter方法的返回值,也就是刚才的CNode节点,arg=1。node.predecessor()返回CNode的前置节点,在这里也就是head节点,所以p==head成立,进而进行tryAcquire操作,即争用锁, 如果获取成功,则进入if方法体,看下接下来的操作:

  1. 将CNode设置为头节点。
  2. 将CNode的前置节点设置的next设置为null。

上面操作即完成了FIFO的出队操作。

从上面的分析可以看出,只有队列的第二个节点可以有机会争用锁,如果成功获取锁,则此节点晋升为头节点。对于第三个及以后的节点,if (p == head)条件不成立,首先进行shouldParkAfterFailedAcquire(p, node)操作(争用锁失败的第二个节点也如此), 来看下源码:

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL)/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;if (ws > 0) {/** Predecessor was cancelled. Skip over predecessors and* indicate retry.*/do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {/** waitStatus must be 0 or PROPAGATE.  Indicate that we* need a signal, but don't park yet.  Caller will need to* retry to make sure it cannot acquire before parking.*/compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}

shouldParkAfterFailedAcquire方法是判断一个争用锁的线程是否应该被阻塞。它首先判断一个节点的前置节点的状态是否为Node.SIGNAL,如果是,是说明此节点已经将状态设置如果锁释放,则应当通知它,所以它可以安全的阻塞了,返回true。

如果前节点的状态大于0,即为CANCELLED状态时,则会从前节点开始逐步循环找到一个没有被“CANCELLED”节点设置为当前节点的前节点,返回false。在下次循环执行shouldParkAfterFailedAcquire时,返回true。这个操作实际是把队列中CANCELLED的节点剔除掉。

前节点状态小于0的情况是对应ReentrantLock的Condition条件等待的,这里不进行展开。

如果shouldParkAfterFailedAcquire返回了true,则会执行:“parkAndCheckInterrupt()”方法,它是通过LockSupport.park(this)将当前线程挂起到WATING状态,它需要等待一个中断、unpark方法来唤醒它,通过这样一种FIFO的机制的等待,来实现了Lock的操作。

释放锁

通过ReentrantLock的unlock方法来看下AQS的锁释放过程。来看下源码:

public void unlock() {sync.release(1);}public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

unlock调用AQS的release()来完成, AQS的如果tryRelease方法由具体子类实现。tryRelease返回true,则会将head传入到unparkSuccessor(Node)方法中并返回true,否则返回false。首先来看看Sync中tryRelease(int)方法实现,如下所示:

protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free
}

这个动作可以认为就是一个设置锁状态的操作,而且是将状态减掉传入的参数值(参数是1),如果结果状态为0,就将排它锁的Owner设置为null,以使得其它的线程有机会进行执行。
在排它锁中,加锁的时候状态会增加1(当然可以自己修改这个值),在解锁的时候减掉1,同一个锁,在可以重入后,可能会被叠加为2、3、4这些值,只有unlock()的次数与lock()的次数对应才会将Owner线程设置为空,而且也只有这种情况下才会返回true。

在方法unparkSuccessor(Node)中,就意味着真正要释放锁了,它传入的是head节点(head节点是占用锁的节点),看下源码:

    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)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.*/Node s = node.next;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);}

首先会发生的动作是获取head节点的next节点,如果获取到的节点不为空,则直接通过:“LockSupport.unpark()”方法来释放对应的被挂起的线程,这样一来将会有一个节点唤醒后继续进入循环进一步尝试tryAcquire()方法来获取锁。


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

相关文章

cas 计算器 android,GeoGebra CAS计算器

GeoGebra CAS计算器app是一款非常不错的几何cas计算工具&#xff0c;支持精确解,展开,分解因式等计算功能&#xff0c;图形分解解析&#xff0c;坐标轴&#xff0c;曲线&#xff0c;参数信息详细&#xff0c;需要的可以下载GeoGebra CAS计算器app使用&#xff01; 软件简介 求解…

【Java里的CAS机制】什么是CAS,CAS机制

CAS 一、什么是CAS二、CAS的三大缺点ABA问题 一、什么是CAS 前面两篇文章提到CAS操作&#xff0c;那么CAS操作到底是什么东西呢&#xff1f;今天我们来了解一下CAS机制 CAS&#xff08;Compare-And-Swap&#xff09;,它是一条CPU并发原语&#xff0c;用于判断内存中某个位置的…

CAS机制是什么?

首先我们需要了解为什么有CAS机制的存在&#xff1f;那我们就不得不先知道互斥同步和非阻塞同步。 互斥同步 互斥同步面临的主要问题是进行线程阻塞和唤醒带来的性能开销&#xff0c;因此这种同步也叫阻塞同步。互斥同步属于悲观的并发策略&#xff0c;其总是认为只要不做同步…

CAS单点登录

简介 CAS是Central Authentication Service的缩写&#xff0c;中央认证服务&#xff0c;一种独立开放指令协议。CAS 是 耶鲁大学&#xff08;Yale University&#xff09;发起的一个开源项目&#xff0c;旨在为 Web 应用系统提供一种可靠的单点登录方法&#xff0c;CAS 在 200…

什么是CAS(乐观锁)

本文讲解CAS机制&#xff0c;主要是因为最近准备面试题&#xff0c;发现这个问题在面试中出现的频率非常的高&#xff0c;因此把自己学习过程中的一些理解记录下来&#xff0c;希望能对大家也有帮助。 什么是悲观锁、乐观锁&#xff1f;在java语言里&#xff0c;总有一些名词看…

CAS机制

文章目录 1、什么是CAS&#xff1f;2、CAS实现原子操作的3大问题&#xff1f;3、Unsafe类4、AtomicReference5、CAS——自旋锁 1、什么是CAS&#xff1f; CAS的全称是 Compare And Swap&#xff08;比较再交换&#xff0c;确切一点称之为&#xff1a;比较并且相同再做交换&…

Java 中 CAS 是什么,有哪些实际应用场景

CAS 是什么 CAS&#xff08;Compare And Swap&#xff09;是一种并发编程中的原子操作&#xff0c;用于实现多线程之间的同步。在 Java 中&#xff0c;CAS 操作通过 sun.misc.Unsafe 类实现。 CAS 操作是一种乐观锁机制&#xff0c;它假设对于共享变量的修改操作不会引起冲突…

Java 什么是 CAS? 通俗易懂

Java 并发机制实现原子操作有两种&#xff1a; 一种是锁&#xff0c;一种是CAS。 CAS是Compare And Swap&#xff08;比较并替换&#xff09;的缩写。 java.util.concurrent.atomic中的很多类&#xff0c;如&#xff08;AtomicInteger AtomicBoolean AtomicLong等&#xff09;都…

什么是CAS?CAS有什么缺点?

文章目录 什么是CASCAS 举例说明CAS 底层实现CAS缺陷 什么是CAS CAS 的全称是 Compare And Swap 即比较交换&#xff0c;其算法核心思想如下函数&#xff1a;CAS(V,E,N) 参数&#xff1a; V 表示要更新的变量E 预期值N 新值 如果 V 值等于 E 值&#xff0c;则将 V 的值设为 …

【ceph】存储领域的CAS是什么?什么是CAS|Open CAS|缓存加速软件

目录 什么是CAS 出现原因和应用场景&#xff1a; 初始Open CAS What SPDK Block Device Open CAS Linux Whats Cache? Whats Cache Object? Cache operations Cache Configuration Cache Mode Cache line size Whats Core? Whats Cache line&#xff1f; Cach…

java常见面试考点(二十五):CAS是什么

java常见面试考点 往期文章推荐&#xff1a;   java常见面试考点&#xff08;二十&#xff09;&#xff1a;Elasticsearch 和 solr 的区别   java常见面试考点&#xff08;二十一&#xff09;&#xff1a;单点登录   java常见面试考点&#xff08;二十二&#xff09;&…

CAS是什么?彻底搞懂CAS

CAS&#xff08;Compare-And-Swap&#xff09;,它是一条CPU并发原语&#xff0c;用于判断内存中某个位置的值是否为预期值&#xff0c;如果是则更改为新的值&#xff0c;这个过程是原子的。 CAS基本原理 CAS并发原语体现在Java中就是sun.misc.Unsafe类中的各个方法。调用UnSa…

(一)CAS是什么?

前言 随着企业数字化转型的不断发展&#xff0c;应用系统越来越多&#xff0c;每个系统都有一套用户系统&#xff0c;用户在操作不同的系统时&#xff0c;需要多次登录&#xff0c;而且每个系统的账号都不一样&#xff0c;这对于用户来说&#xff0c;很不方便。这时候要做到 在…

CAS是什么

目录 没有CAS之前 使用CAS之后 CAS是什么 CAS底层原理&#xff1a;unsafe类 Unsafe new AtomicInteger().getAndIncrement()流程 CAS缺点 1 循环时间长开销很大 2 ABA问题 ABA代码演示 如何解决&#xff1f;&#xff1a;通过AtomicStampedReference版本号 3不能保…

CAS 是什么?

CAS又称 自旋锁、无锁&#xff0c;是一种乐观锁 compare and swap 的缩写 意为: 比较并交换 , 实现并发算法的常用技术 , 就是说我不用加锁 , 也能保证 ( 加锁会影响效率&#xff0c;可以考虑使用原子操作类 ) 原子性 , 当多个线程尝试使用 CAS 同时更新同一个变量时&#xf…

什么是CAS

文章目录 一、CAS是什么二、CAS 可以解决什么问题三、CAS实现原子操作的问题 一、CAS是什么 CAS的全称为compare and swap 或者compare and exchange,意思为比较和交换。CAS流程如下&#xff1a; 假设我们有一个共享变量i&#xff0c;初始值为0。我们现在要对i进行加1的操作…

metasploit图形化工具 Armitage

Metasploit默认使用PostgreSQL存储渗透测试所需的数据表&#xff0c;所以在启动Armitage之前需要首先启动PostgreSQL服务和Metasploit服务&#xff0c;然后再启动armitage&#xff0c;弹出对话框询问是否连接Metasploit的RPC信道&#xff0c;依次选择“connect”、“是”选项。…

ARM..

一 关于arm 1 arm的三种含义 (1) 一个公司的名称 Advanced RISC Machine (2) 一类处理器的统称 (3) 一种技术的名称 &#xff08;RISC&#xff09; ARM是以一家设计处理器的公司&#xff0c;这家公司设计的处理器统称为ARM&#xff0c;它们使用的指令集是RISC&#xff08;精简指…

01-Introducing the Arm architecture

快速链接: . 👉👉👉 个人博客笔记导读目录(全部) 👈👈👈 付费专栏-付费课程 【购买须知】:【精选】ARMv8/ARMv9架构入门到精通-[目录] 👈👈👈目录 1、Overview2、About the Arm architecture3、架构(architecture)到底是什么意思呢

傻瓜式渗透Armitage的使用

目录 Armitage的基本介绍 安装 1 启动Armitage 1、Armitage启动前初始化&#xff1a; 2、Armitage启动的三种方法&#xff1a; 2 使用Armitage生成被控端和主控端 1、Armitage的工作界面&#xff1a; &#xff08;1&#xff09;区域1&#xff1a; &#xff08;2&#…