收藏 分销(赏)

Java多线程-知识点梳理和总结-超详细-面试知识点.docx

上传人:二*** 文档编号:4518330 上传时间:2024-09-26 格式:DOCX 页数:25 大小:38.99KB
下载 相关 举报
Java多线程-知识点梳理和总结-超详细-面试知识点.docx_第1页
第1页 / 共25页
本文档共25页,全文阅读请下载到手机保存,查看更方便
资源描述
Java多线程知识点梳理和总结面试知识点 一、volatile 1.可见性 (1)当被volatile修饰的变量进行写操作时,这个变量将会被直接 写入共享内存,而非线程的专属存储空间。 ①根本原因是在写入后执行了一个空操作,使得cpu的cache写入内 存 (2)当读取一个被volatile修饰的变量时,会直接从共享内存中 读,而非线程专属的存储空间中读。 2 .有序性 (1) volatile在指令间加上了内存屏障,内存屏障指的是重排序的 时候不能把后面的指令重排序到内存屏障之前的位置。 3 .原子性(不能保证)(1)原子性指的是一组操作必须一起完成,中途不能被中断。 (2) volatile能确保long、double读写的原子性 ①java内存模型保证声明为volatile的long和double变量的get和 set操作是原子的。 4 .应用场景 (1)多个变量之间或者某个变量的当前值与修改后值之间没有约 束。 ①状态标志 ②全局变量 ③一读多写 c. 一个 segment 包含了一个 hashEntry 链表 2) hashEntry a.链表结构的元素 b. hashEntry的成员变量除了 value都定义为final a)为了维护链表结构,防止并发问题 ② JDK1.8 1) Node数组 2)链表 3)红黑树(2)常用方法底层实现(JDK1.7) ① put() 1)流程 a. 1.首先Hash定位到Segment b. 2.对当前Segment加锁,如果Segment中元素的数量超过了阈 值,那么需要进行扩容并且进行rehash c. 3.再定位到链表头部 ② get() 1)不用加锁,是非阻塞的 a.因为共享变量都定义为了 volatile a)根据java内存模型的happen before原那么,对volatile字段的写 入操作先于读操作,即使两个线程同时修改和获取volatile变量,get 操作也能拿到最新的值,这是用volatile替换锁的经典应用场景 b) volatile能够保证内存可见性 2)需要进行两次Hash操作,第一次Hash定位到Segment,第二次 Hash定位到元素所在的链表的头部 3)定位到链表头部之后根据key取出对应的value值 a.如果取出的value是null,那么对取出value这一过程进行加锁 (lock()) a)取出的value是null的原因是可能现在正在进行put操作 b.如果不是value,那么直接返回value值 (3) remove)) 1)因为HashEntry中的next是final的,一经赋值以后就不可修 改,所以在定位到待删除元素e的位置以后,程序就将待删除元素前 面的那一些元素全部复制一遍,然后再一个一个重新接到链表上去。 尾结点指向e的下一个结点。e后面的结点不需要复制,它们可以重 用。e之前的元素在remove。之后为remove之前的逆置 ④ size() 1) size()操作涉及到多个segment 2) size操作就是遍历了两次Segment,每次记录Segment的 modCount值,然后将两次的modCount进行比拟,如果相同,那么表示 期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同, 那么把这个过程再重复做一次,如果再不相同,那么就需要将所有的 Segment都锁住,然后一个一个遍历了(3)特点 ①key和value都不可以为null ②get()方法不加锁,是非阻塞的 ③是线程平安的 ④JDK 1.8使用了 CAS操作来支持更高的并发度,在CAS操作失败 时使用内置锁synchronizedo并且JDK 1.8的实现也在链表过长时会转 换为红黑树。 (4)常用方法底层实现(JDK1.7) 2. ConcurrentSkipListMap 和 ConcurrentSkipListSet 3. ConcurrentLinkedQueue 4. CopyOnWriteArrayList 和 CopyOnWriteArraySet 六、线程分类 1 .用户线程一般是程序中创立的线程 2 .守护线程 (1)为用户服务的线程,当所有用户线程停止时才会被终止,如 JVM的垃圾回收(2)通过Thread.setDaemon(true)方法设置守护线程 七、Java内存模型 1 .主内存 2 .工作内存 (1)线程开始运行时会将所需的变量从主内存中拷贝一份到工作 内存中,在线程运行结束后再写入主内存 3 .主内存和工作内存的交互read,writejock,unlock,assgin,use,load,save 4 .三大特征(1)可见性 ①一条线程修改完一个共享变量后,另一个线程假设访问这个变量将 会访问到修改后的值(2)有序性 ① Happens-Before 规那么 1)如果一个操作执行的结果需要对另一个操作可见,那么这两个 操作必须要存在一个happen-before的关系 ②重排序 1)为了减少内存操作速度远慢于CPU运行速度所带来的CPU空置 的影响,虚拟机会按照自己的一些规那么对代码的执行顺序进行调整, 从而提高执行效率。 ③程序代码执行的结果不受JVM指令重排序的影响(3)原子性 ①一组操作必须一起完成,中途不能被中断。 八、synchronized 1 . 一个重量级的可重入锁 (1)监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核 心态,这个状态之间的转换需要相比照拟长的时间,时间本钱相对较 高,这也是为什么早期的synchronized效率低的原因 2 .作用方式(1)作用于代码块 (2)作用于实例方法 ①持有的是当前对象实例的锁(3)作用于静态方法 ①持有的是静态对象的锁 3 .底层实现(1)作用于方法(显式同步) ①JVM可以从方法区中的方法表结构中的ACC_SYNCHRONIZED访问 标志区分一个方法是否同步方法。 (2)作用于代码块(隐式同步) ①编译时,在代码块的前后加上monitorenter和monitorexit 1)为了保证在方法异常完成时monitorenter和monitorexit指令依 然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常 处理器声明可处理所有的异常,它的目的就是用来执行monitorexit指 令ObjectMontior ① waitSet ② EntryList (3) owner ④ count 4 .线程中断与synchronized (1)线程的中断操作对于正在等待获取的锁对象的synchronized方 法或者代码块并不起作用 5 .等待唤醒机制与synchronized (1)在使用notify/notifyAII和wait这3个方法时,必须处于 synchronized 代码块或者synchronized 方法中(2) notify/notifyAII 和 wait 方法都依赖于 monitor 6. java对象头Mark Word ①对象的hashCode ②CG年代 ③锁信息(偏向锁,轻量级锁,重量级锁) ④GC标志 ⑤指向monitor的指针Class Metadata Address ①指向对象实例的指针 九、线程 1 .线程中断public static boolean interrupted() ①测试当前线程是否已经中断,并将线程状态设置为falsepublic boolean islnterruptedf) ①测试线程是否已经中断。线程的中断状态不受该方法的影响public void interrupt)) ①中断线程,设置中断标识为为trueInterruptedException ①对线程调用interrupt。时,如果该线程处于阻塞或者等待状态, 那么就会抛出 InterruptedException 2 .线程状态(1)新建 ①继承Thread类创立线程 ②实现Runnable接口创立线程 ③使用Callable和Future创立线程 ④使用线程池例如用Executor框架 (2)运行 (3)阻塞 ①同步阻塞 1)竞争锁失败 ②等待阻塞 1)调用 wait,lockSupport.park。等方法 ③其他阻塞 (4)就绪 ①调用 thread.start() (5)结束 ①线程运行完毕 3.线程间通信 (1) wait/notify/notifyAII (2) synchronized/lock (3) thread 方法 ① thread.join() ② thread.yiled() (4)管道通信 ①管道流pipeStream 1)是一种特殊的流,用于在不同线程间直接传送数据。一个线程 发送数据到输出管道,另一个线程从输入管道中读数据。 (5)同步工具类 ① CountDownLatch 1) CountDownLatch允许一个或多个线程等待其他线程完成操作 a. countDownf) a) count 数减 1 b. awaitf) a)线程阻塞直到count等于0 2)应用场景 a.开启多个线程分块下载一个大文件,每个线程只下载固定的一 截,最后由另外一个线程来拼接所有的分段。 b.应用程序的主线程希望在负责启动框架服务的线程已经启动所 有的框架服务之后再执行。 c.确保一个计算不会执行,直到所需要的资源被初始化。 ② CyclicBarrier 1) CyclicBarrier (int ),参数表示屏障拦截的线程数量,每个线程 调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被 阻塞。 2)当CyclicBarrier的count数等于。的时候,阻塞的线程都继续执 行 3)应用场景 a.多线程计算 (3) Semphore 1)可以控制同时访问的线程个数,它维护了一组“许可证”。 a. acquire() a)消费一个许可证。如果没有许可证了,会阻塞起来 b. release)) a)添加一个许可证 2)应用场景 a.可以用于做流量控制,特别是公用资源有限的应用场景,比方 数据库连接 ④ CountDownLatch 和 CyclicBarrier 的区另U 1) CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数 器可以使用reset。方法重置 2) CountDownLatch.await 一般阻塞主线程,而 CyclicBarrierton 一般 阻塞工作线程 3) CountDownLatch主要用于描述一个或者多个线程等待其他线程 执行完毕 4) CyclicBarrier主要用于描述多个线程之间相互等待 ⑤ Exchanger 1) Exchanger用于进行线程间的数据交换。它提供一个同步点,在 这个同步点,两个线程可以交换彼此的数据。这两个线程通过 exchange方法交换数据,如果第一个线程先执行exchange。方法,它会 一直等待第二个线程也执行exchange方法,当两个线程都到达同步点 时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对 方。 4.线程平安 (1)所谓的线程平安问题,本质在于线程对共享变量的操作的原 子性,可见性,有序性不能同时满足,因此解决线程平安问题的关键 在于使其同时满足以上三个特征 十、线程池(Executor) 1 .线程池基本概念 (1)线程池可以看做是线程的集合,请求到来时线程池给这个请 求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不 是销毁)。这样就实现了线程的重用。 2 . ThreadPoolExecutor(1)构造函数 ①阻塞队列策略 1)同步移交 a. SynchronousQueue a)不存储元素的阻塞队列,因此超出核心线程数的任务会创立新 的线程来指执行。 2)无界队列 a.常用的为无界的LinkedBlockingQueue b.当任务耗时较长时可能会导致大量新任务在队列中堆积最终导 致00M c. DelayedWorkQueue a) 一个按超时时间升序排序的队列 b)使用了优先级队列的无界阻塞队列,支持延时获取,所谓延时 队列就是消费线程将会延时一段时间来消费元素。队列里的元素要实 现 Delay 接口。 3)有界队列 a.常见队列 a) ArrayBlockingQueue i.遵循FIFO的队列 二、JUC 1. LockReentrantlock ①特点 1)可实现公平锁 a.在构造函数中设置 a)通过fair这个boolean型变量设置 b.是通过hasQueuedPredecessors。函数实现的 a)这个函数会判断当前获取锁的线程是否是请求队列的首部线程 2)绑定多个条件(Condition) 3)等待可中断 4)可重入锁 a.通过 setExclusiveOwnerThread。实现 ②内部类 1) Reentrantlock 默认实现在 sync 中(sync 继承了 AQS) 2)非公平锁 NonfairSync (继承 sync) 3)公平锁 FairSync(继承 sync)ReentrantReadWriteLock ①特点 1) state的变量高16位是读锁,低16位是写锁。 2)读锁不能升级为写锁 3)写锁可以降级为读锁 4)当访问方式是读取操作时,使用读锁即可,当访问方式是修改操作时,那么使用写锁 b) LinkedBlockingQueue 1 .有节的 LinkedBlockingQueue c) PriorityBlockingQueue 1 .优先级队列 b.可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量(因为任务数量少了) ②拒绝策略 1)丢弃当前任务 2)丢弃最老的任务 3)直接抛出异常 4)抛回调用者的线程处理(2)线程池状态 ① running 1)自然是运行状态,指可以接受任务执行队列里的任务 ② shutdonw 1) SHUTDOWN指调用了 shutdown。方法,不再接受新任务了,但 是阻塞队列里的任务得执行完毕。 ③ stop 1) STOP指调用了 shutdownNow()方法,不再接受新任务,同时抛 弃阻塞队列里的所有任务并中断所有正在执行任务。 ④ tidying 1)所有任务都执行完毕,在调用shutdown()/shutdownNow()中都 会尝试更新为这个状态。 ⑤ terminated 1)当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已 经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED 状态 (3)线程池的配置 ①10密集型 1)尽量使用较小的线程池,一般为CPU核心数+1 ②Cpu密集型 1)可以使用稍大的线程池,一般为2*CPU核心数。 ③混合型 3.常见的线程池newFixedThreadPool ①固定大小的线程池,有新任务时,根据当前线程池的线程数量确 定后续步骤 ②创立参数 1) LinkedBlockingQueue(无界) 2)线程存活时间:永久存活 3)核心线程数:n(用户指定) 4)最大线程数:n(用户指定)newCachedThreadPool ①当有新任务时,直接新建线程 ②创立参数 1) SynchronousQueue 2)线程存活时间:60s 3)核心线程数:0 4)最大线程数:Interget.MAX_VALUEnewSingleThreadExecutor ①创立单个线程的线程池。 ②创立参数 1) LinkedBlockingQueue(无界) 2)线程存活时间:永久存活 3)核心线程数:1 4)最大线程数:1NewScheduledThreadPool: ①创立一个定长线程池,支持定时及周期性任务执行。 ②创立参数 1) DelayedWorkQueue a. 一个按超时时间升序排序的队列 b.使用了优先级队列的无界阻塞队列,支持延时获取,所谓延时 队列就是消费线程将会延时一段时间来消费元素。队列里的元素要实 现 Delay 接口。 2)线程存活时间:永久存活 3)核心线程数:用户指定 4)最大线程数:lnteger.MAX_VALUE 4 .线程池的关闭Shutdown ①线程池状态变为shutdownShutdownNow ①线程状态变为stop(3)中断线程池中的某一个线程的方法 ①可以通过使用submitf)方法来提交一个线程,它会返回一个 Future<?>对象,通过调用该对象的cancel(true)方法就可以中断线程。 5 .常见接口和类Executor 接口 ①只有一个void excute(Runnable task)方法,用户执行任务ExecutorService ① Executor■的子接口,定义了 sumbit,invokeAII/nvokeAny 等方法AbstractExecutorService ①实现了 sumbit,invokeAII。等方法 6 .源码Worker ①Worker是线程池中的线程 ②继承AQS,实现runnable 1)既是一个可执行的任务,又可以到达锁的效果 ③初始state=-l ④这样构造的原因主要是为了实现对中断的控制 1) 1.worker未运行时 a. 1.shutdown。线程池时,会对每个worker tryLock。上锁,tryAcquire是尝试通过CAS将state由0设置为1,因此会失败 b. 2.shutdownNow()线程池时,不用tryLock。上锁,但调用worker.interruptlfStarted()^lh worker 时也要求 state=0,因此也会失败 2) 2.worker 运行时 3) runWorker中,会对正在运行中的worker加锁,所以如果调用 了 shutdown。方法,中断也会失败,但是如果调用shutdownNow。方 法,该方法会通过worker.interruptlfStarted来中断任务addWorker ①1.判断线程池状态,如果状态正常,进入下一步 ②2.比拟当前线程池的线程数和最大线程数/核心线程数的大小(由 参数core确定比拟对象),如果没有超过的话,进入下一步,否那么返回 false ③3.在线程池自带的成员变量ReentrantLock的加锁的情况下,向 Workers的HashSet中添加新创立的worker实例,添加完成后解锁, 并start该worker实例,worker.start()方法底层其实调用的就是 runWorker。方法 ④基本概念:创立新线程并执行runWorker ①1.将state设置为0 ②2.在mainLock.lock的情况下,进行task.run ③ 3.在 finally 块中释放锁 mainLock.unlock ④4.在使用getTask方法去阻塞队列中获取锁 十一、线程死锁 1 .死锁产生的四个必要条件 (1)互斥条件:资源是独占的且排他使用,进程互斥使用资源, 即任意时刻一个资源只能给一个进程使用,其他进程假设申请一个资 源,而该资源被另一进程占有时,那么申请者等待直到资源被占有者释 放。 (2)不可剥夺条件:进程所获得的资源在未使用完毕之前,不被 其他进程强行剥夺,而只能由获得该资源的进程资源释放。 (3)请求和保持条件:进程每次申请它所需要的一局部资源,在 申请新的资源的同时,继续占用已分配到的资源。 (4)循环等待条件:在发生死锁时必然存在一个进程等待队列 {Pl,P2,...,Pn}淇中P1等待P2占有的资源,P2等待P3占有的资 源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一 个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一 个进程所申请的资源。 2 .解决方法(1)解决任一条件 ②内部类 1) ReentrantReadWriteLock 默认实现在 sync 中(sync 继承了 AQS) 2) NonfairSync 3) FairSync 4) WriteLock (实现 Lock 接口) 5) ReadLock (实现 Lock 接口)synchronized 和 lock 的区别 ①synchronized是关键字,lock是接口 ②发生异常时,synchronized会自动释放锁,lock需要手动释放锁 ③lock能够知道是否成功获取到锁,synchronized那么不行 1)通过tryAcquire。去尝试获取锁,获取成功那么设置锁状态并返回true,否那么返回false0 ④在竞争激烈的情况下,lock效率更高 1) JDK1.5之前Lock效率高很多 2) JDK1.6之后效率差不多 ⑤lock可以实现等待可中断,synchronized不可以 1)通过tryLock(long time, TimeUnit unit)设置等待时间,如果规定时 间内没获取到锁,才返回false ⑥lock可以实现公平锁 ⑦lock可以实现读写锁 2. Atomic(1)对数据进行操作时(使用CAS来保证)可以保证原子性 (2)存在CAS的ABA问题 ①使用AtomicStampedReference的版本号机制来管理(3)通过unsafe实现的包装类 ①Unsafe底层实际上是调用C代码,C代码调用汇编,最后生成出 一条CPU指令cmpxchg,完成操作 ② CompareAndSwap...() (4) JDK增加了 LongAdder等四个类,高并发时效率更高,但是消 耗空间更多 3. AQS (1)一个抽象的队列式的同步器,定义了一套多线程访问共享资 源的同步器框架(实现锁的框架),许多同步类实现都依赖于它,本 质上其实就是对于state的获取和释放 (2) ReentrantLock/ReentrantReadWriteLock/Semaphore/ CountDownLatch的实现都依赖于它(3)数据结构 ①共享资源 volatile int state ②默认实现了 FIFO线程等待队列,底层是双向链表(4)线程模式 ①独享模式 1) tryAcquire(int)/tryAcquireShared(int) ②共享模式 1) tryRelease/tryReleaseShared(int) ③既可独占,也可共享(ReentrantReadWriteLock) 4.线程池相关callable ①一般和ExecutorService配合来使用 ②有返回值future ①判断任务是否完成 1) future.isDonef) ②返回执行结果 1) future.get() ③中断线程的执行 1) future.cancel))futureTask ①future接口的唯一实现类 ②既可以作为Runnable被线程执行,又可以作为Future得到 Callable的返回值。 5. CAS算法 (1) CAS需要有3个操作数:内存地址V,旧的预期值A,即将要 更新的目标值B。,CAS指令执行时,当且仅当内存地址V的值与预期 值A相等时,将内存地址V的值修改为B,否那么就什么都不做。整个 比拟并替换的操作是一个原子操作。 ①使用AtomicStampedReference的版本号机制来管理ABA问题 ①如果内存地址V初次读取的值是A,并且在准备赋值的时候检查 到它的值仍然为A,但可能期间它已经被修改过了 ②解决方法:使用AtomicStampedReference的版本号机制来管理 (3)通常将CAS用于同步的方式是从地址V读取值A,执行多步计 算来获得新值B,然后使用CAS将V的值从A改为B。如果V处的值尚 未同时更改,那么CAS操作成功。 (3) CAS操作是一条CPU指令,不会被打断,所以是原子操作 三、锁分类 1 .乐观锁,悲观锁(1)乐观锁 ①CAS算法(2)悲观锁 ① synchronized 2 .独享锁,共享锁 3 .公平锁,非公平锁 4 .可重入锁(1)线程得到一个对象锁后再次请求该对象锁,是允许的 (2)实现方法 ①为每一个锁关联一个获取计数器和一个所有者线程,在获取锁的 时候判断owner,如果是相同的owner,那么count加1 5 .分段锁(1)通过分段锁的形式来细化锁的粒度,从而实现高效的并发操 作,例如 concurrentHashMap 6 .锁优化(1)偏向锁 ①CAS操作:将线程ID保存在对象的Mark Word中 1)如果成功,那么说明该线程已经获取了对象的偏向锁 ②有其他线程获取对象锁时失效 1)对象锁定时膨胀为轻量级锁 2)对象未锁定时恢复到未锁定状态 ③经验依据:在大多数情况下,锁不仅不存在多线程竞争,而且总 是由同一线程屡次获得(2)轻量级锁 ①CAS操作:将对象的Mark Word更新为指向Lock Record的指针 1)如果成功,那么该线程已经获取了对象的轻量级锁 2)如果失败,检查对象的Mark Word是否指向当前线程 a.如果指向当前线程,那么进入同步代码块 b.如果没有,那么膨胀为重量级锁 ②如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为 重量级锁。 ③经验依据:对绝大局部的锁,在整个同步周期内都不存在竞争(3)重量级锁 (4)自旋锁 ①尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获 取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPUo(5)锁粗化 ①如果一串零碎的操作都对同一个对象加锁,将会把加锁同步的范 围扩展(粗化)到整个操作序列的外部(6)锁消除 ①对于一些代码上要求同步,但实际上并不需要同步的锁进行消除 四、ThreadLocal 1 . ThreadLocal提供了线程的局部变量,且不会和其他线程的局部 变量进行冲突,实现了线程的数据隔离。 2 .基本用法set(value) ① 首先获取当前thread的ThreadLocalMap 1)如果map已经初始化,那么将kv存入map中 2)否那么初始化map(此时构造函数已经将kv存入)get() (1) removedinitialValue() 3 .底层实现ThreadLocalMap ThreadLocal 的内部类 (1) set():向 ThreadLocalMap 设置值,key 是 ThreadLocal 对象, 值是传递进来的对象get。:从 ThreadLocalMap 获取值,key 是 ThreadLocal 对象 4 . ThreadLocalMap(1)用Entry进行存储,Entry的key的类型为ThreadLocal (2)每个Thread维护了一个ThreadLocalMap的成员变量,这是实 现ThreadLocal的核心hash冲突解决方法:线性探测法 5 .内存泄漏 (1) ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key 会被回收,而Value不会回收,这时候ThreadLocalMap里面就会存在 key为null但是value不为null的entry项。如果创立ThreadLocal的线 程一直持续运行,那么这个Entry对象中的value就有可能一直得不到 回收,发生内存泄露。 (2)解决方法 ①调用threadLocal.remove。方法来清理key为null的元素(3)根本原因 ①由于ThreadLocalMap的生命周期跟Thread 一样长,如果没有手 动删除对应key的value就会导致内存泄漏,而不是因为弱引用(4)为什么key要用弱引用 ①引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持 有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回 收,导致Entry内存泄漏。 6 .应用场景(1)单个线程 (2)线程上下文信息存储(3)数据库连接 (3) session 管理 五、并发容器 1. concurrentHashMap(1)数据结构 ① JDK1.7 1) segment a.继承了 ReentrantLock b. 一个 concurrentHashMap 包含了一个 segment 数组
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 考试专区 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服