Java并发编程之锁的艺术:面试与实战指南(三)

Java并发编程之锁的艺术:面试与实战指南(三)

文章目录

  • Java并发编程之锁的艺术:面试与实战指南(三)
    • 前言
    • 十七、Java中线程和进程的区别是什么?
    • 十八、什么是Java内存模型(JMM)?它在并发编程中有什么作用?
    • 十九、volatile关键字的作用是什么?能保证线程安全吗?
    • 二十、什么是线程局部变量(ThreadLocal)?它在什么场景下使用?
    • 二十一、什么是阻塞队列?它在Java并发包中是如何实现的?
        • ArrayBlockingQueue:
        • LinkedBlockingQueue:
        • PriorityBlockingQueue:
        • DelayQueue:
        • LinkedTransferQueue:
    • 二十二、什么是Future和Callable?它们在并发编程中有什么应用?
    • 二十三、什么是ForkJoinPool?它适用于哪些场景?
    • 二十四、如何在Java中实现线程间的通信?
    • 二十五、什么是CAS操作?它在Java并发中有什么应用?
    • 二十六、Java中的原子类有哪些?它们是如何保证原子性的?

🌈你好呀!我是 山顶风景独好
💝欢迎来到我的博客,很高兴能够在这里和您见面!
💝希望您在这里可以感受到一份轻松愉快的氛围!
💝不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
🚀 欢迎一起踏上探险之旅,挖掘无限可能,共同成长!

前言

本系列地址:
Java并发编程之锁的艺术:面试与实战指南(一)
Java并发编程之锁的艺术:面试与实战指南(二)
Java并发编程之锁的艺术:面试与实战指南(三)

十七、Java中线程和进程的区别是什么?

  1. 定义与关系:进程是程序在处理机上的一次动态执行过程,而线程是进程的一个实体,是CPU调度和分派的基本单位。一个操作系统中可以拥有多个进程,一个进程里可以拥有多个线程,线程在进程内执行。
  2. 资源占用:进程拥有自己独立的内存空间和系统资源,而线程使用进程的内存空间,并和该进程的其他线程共享这个空间。
  3. 通信方式:线程可以使用wait(), notify(), notifyAll()等方法直接与其他线程(同一进程)通信;而进程需要使用“进程间通信”(IPC)来与操作系统中的其他进程通信。
  4. 创建与切换开销:由于进程拥有独立的资源,因此创建和销毁进程的开销通常比线程大。而线程是处理器任务调度和执行的基本单位,线程的创建、切换和销毁的开销相对较小。
  5. 独立性:进程是系统中独立存在的实体,是一个能独立运行的单位;而线程不拥有系统资源,只拥有一点在运行中必不可少的资源,但可以与同属一个进程的其他线程共享进程所拥有的全部资源。

十八、什么是Java内存模型(JMM)?它在并发编程中有什么作用?

Java内存模型(JMM)是Java虚拟机(JVM)规范中定义的一种内存模型,它描述了Java程序中各种变量(包括实例字段、静态字段和数组元素)的访问规则,以及在多线程环境中这些变量的可见性、原子性和有序性的保证。JMM并不真实存在,它仅仅是一组规则或规范,通过这组规范定义了程序中各个变量的读写访问方式。

在并发编程中,JMM的作用主要体现在以下几个方面:

  • 屏蔽系统和硬件的差异:由于不同的硬件生产商和不同的操作系统下,内存的访问逻辑有一定的差异,JMM能够屏蔽这些差异,使得Java程序能够在不同的系统环境下达到相同的访问结果,实现“一次编写,到处运行”的目标。
  • 保证多线程之间对共享变量操作的原子性、可见性和有序性:JMM通过定义如何通过synchronized和其他同步方式来保证这些特性。原子性指的是一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;可见性指的是当一个线程修改了共享变量的值,其他线程能够立即得知这个修改;有序性指的是程序执行的顺序按照代码的先后顺序执行。
  • 定义线程和主内存之间的抽象关系:JMM定义了JVM在计算机内存(RAM)中的工作方式,每个线程都有自己的独立工作内存,里面保存了该线程使用的变量的副本。线程对共享变量的所有操作都必须从自己的工作内存中读写,不能直接从主内存中读写。不同线程之间不能直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

十九、volatile关键字的作用是什么?能保证线程安全吗?

  • volatile关键字的主要作用是确保多线程环境下的变量可见性。当一个变量被声明为volatile时,它会保证修改的值会立即被更新到主内存,当有其他线程需要读取这个变量时,它会去主内存中读取新值。这样可以避免由于线程的工作内存和主内存中的数据不一致而导致的“脏读”问题。

  • 此外,volatile关键字还可以禁止JVM的指令重排优化,这有助于保持程序执行的顺序性。

  • 然而,需要注意的是,虽然volatile关键字可以保证可见性和禁止指令重排优化,但它并不能保证复合操作的原子性。也就是说,如果多个线程同时对同一个volatile变量进行复杂的读写操作(如自增、自减等),仍然可能出现线程安全问题。

二十、什么是线程局部变量(ThreadLocal)?它在什么场景下使用?

线程局部变量(ThreadLocal)是一种特殊的变量类型,它可以让多个线程并发访问时每个线程都有自己的变量副本,互不干扰。这种机制使得每个线程都可以独立地拥有和操作自己的变量副本,而不会影响到其他线程。

在Java中,可以使用ThreadLocal类来实现线程局部变量。ThreadLocal对象通常是一个静态成员变量,可以在多个线程间共享。每个线程通过ThreadLocal对象获取和设置自己独立的变量副本,不会与其他线程的变量产生冲突。每个线程都可以独立地修改自己的副本,而不需要加锁。

线程局部变量的应用场景

  • 线程安全的共享变量:在多线程环境中,如果我们想要在不同的线程间共享一些变量,但又不想让这些变量被多个线程修改,这个时候就可以使用ThreadLocal。例如,在一个网络应用中,我们可能需要在每个线程中维护一个与客户端的连接信息。使用ThreadLocal,我们可以在每个线程中创建一个连接信息的副本,这样每个线程都可以独立地操作自己的连接信息,而不会影响到其他线程。
  • 线程专属的数据:有些时候,我们可能需要在每个线程中维护一些专属的数据,这些数据在其他线程中是不可见的。使用ThreadLocal,我们可以在每个线程中创建一个数据库连接的副本,这样每个线程都可以独立地操作自己的数据库连接,而不会影响到其他线程。

二十一、什么是阻塞队列?它在Java并发包中是如何实现的?

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加操作是:在队列为空时,获取元素的线程会等待队列变为非空;当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。

阻塞队列的实现依赖于Java的内置锁或显式Lock来实现线程间的同步,并使用了Condition来实现线程间的等待/通知机制。具体来说,当队列为空时,消费者线程会调用Condition的await()方法进入等待状态,直到生产者线程向队列中插入了元素并调用Condition的signal()或signalAll()方法唤醒等待的线程;当队列满时,生产者线程也会调用Condition的await()方法进入等待状态,直到消费者线程从队列中取出了元素并唤醒了等待的生产者线程。

JDK 8中阻塞队列的实现:

ArrayBlockingQueue:
  • 这是一个基于数组实现的有界阻塞队列。队列按照先进先出(FIFO)的原则对元素进行排序。
LinkedBlockingQueue:
  • 这是一个基于链表实现的有界阻塞队列。其默认和最大长度为Integer.MAX_VALUE,也就是说,它是一个几乎无界的队列。队列同样按照先进先出的原则进行排序。
PriorityBlockingQueue:
  • 这是一个支持优先级排序的无界阻塞队列。默认情况下,元素按照自然排序(升序)进行排列。如果元素实现了Comparable接口,那么元素就会按照其compareTo()方法的返回值进行排序;如果元素没有实现Comparable接口,但在创建PriorityBlockingQueue时传入了Comparator对象,那么元素就会按照Comparator的compare()方法的返回值进行排序。
DelayQueue:
  • 这是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现,队列中的元素必须实现Delayed接口。在创建元素时,可以指定多久才能从队列中获取当前元素,只有在延时期满时才能从队列中提取元素。队列头的元素是最先“到期”的元素。
LinkedTransferQueue:
  • 这是一个由链表结构构成的无界阻塞TransferQueue队列。相对于其他阻塞队列,它多了tryTransfer和transfer方法。这个队列是LinkedBlockingQueue、SynchronousQueue(公平模式)和ConcurrentLinkedQueue三者的集合体,它综合了这三者的方法,并提供了更加高效的实现方式。

二十二、什么是Future和Callable?它们在并发编程中有什么应用?

  • Callable:Callable是一个接口,它允许我们执行一个任务并返回结果。与Runnable接口不同,Runnable任务不返回任何结果,而Callable可以返回一个V类型的值。Callable的任务通常在线程池中执行,可以利用Future来获取任务的结果。Callable接口只包含一个call()方法,该方法可以抛出异常,这使得错误处理更加灵活。
  • Future:Future是一个接口,用于获取异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。在并发编程中,我们通常将Callable任务提交给线程池执行,并通过Future对象来跟踪任务的状态和结果。

在并发编程中的应用

  • 并行计算:使用Callable和Future可以方便地实现并行计算,将一个大任务拆分为多个小任务并在多个线程中并行执行。通过将计算任务分配给不同的线程,可以提高计算速度和系统的吞吐量。
  • 异步IO:在网络编程和文件处理等场景中,使用Callable和Future可以实现异步IO操作。可以将IO操作封装为Callable任务,并通过Future对象获取IO操作的结果。这样可以充分利用CPU资源,同时不会阻塞主线程。

二十三、什么是ForkJoinPool?它适用于哪些场景?

ForkJoinPool是JDK7提供的一种基于“分治算法”的多线程并行计算框架。其核心思想是将大的任务拆分成多个小任务(即fork),然后再将多个小任务处理汇总到一个结果上(即join),非常像MapReduce处理原理。它特别适用于任务分解与合并的场景。

ForkJoinPool适用于那些可以自然分解为多个独立子任务,并且这些子任务之间不需要太多通信或同步的问题。

使用场景:

  • 并行数组处理:如排序、过滤、映射等。
  • 并行集合处理:如归约操作等。
  • 科学计算中的并行算法:如矩阵乘法、快速傅里叶变换等。

相比于ThreadPoolExecutor,ForkJoinPool可以更好地实现计算的负载均衡,提高资源利用率。例如,当存在一个大任务和多个小任务时,ThreadPoolExecutor可能会导致一个线程忙于大任务,而其他线程则处于空闲状态。而ForkJoinPool则可以将大任务拆分成多个小任务,然后这些小任务被所有的线程执行,从而实现任务计算的负载均衡。

此外,ForkJoinPool还引入了“工作窃取”机制,在多CPU计算机上处理性能更佳。当一个线程完成了自己的任务后,它可以从其他线程的工作队列中“窃取”一个任务来执行,从而充分利用系统资源。

二十四、如何在Java中实现线程间的通信?

  1. 共享变量
    线程之间可以通过共享变量进行通信。但是,必须确保对共享变量的访问是同步的,以防止并发修改导致的数据不一致。可以使用synchronized关键字、Lock接口或Atomic类来确保同步。

  2. wait/notify/notifyAll
    Object类提供了wait(), notify(), 和 notifyAll() 方法,这些方法可以用于在线程之间通信。一个线程可以调用共享对象的wait()方法进入等待状态,直到其他线程调用该对象的notify()或notifyAll()方法将其唤醒。这种方法通常与synchronized一起使用,以确保线程安全。

  3. BlockingQueue
    Java并发包(java.util.concurrent)中的BlockingQueue接口为线程间的通信提供了一种安全高效的方式。BlockingQueue实现类(如ArrayBlockingQueue, LinkedBlockingQueue, PriorityBlockingQueue等)提供了put(), take(), offer(), poll()等方法用于在队列中添加和移除元素。这些方法在队列为空或满时会阻塞线程,从而实现线程间的通信。

  4. Semaphore
    Semaphore是一个基于计数的信号量,可以用来控制对多个共享资源的访问。它也可以用于实现线程间的通信。通过减少信号量的值(acquire()方法)来阻塞线程,通过增加信号量的值(release()方法)来唤醒线程。

  5. CyclicBarrier
    CyclicBarrier是一个可以让一组线程互相等待,直到所有线程都到达某个公共屏障点的同步工具。在屏障点处,线程可以选择继续执行或执行一些特殊操作,从而实现线程间的通信。

  6. CountDownLatch
    CountDownLatch是一个同步辅助类,允许一个或多个线程等待一组其他线程完成操作。当调用countDown()方法时,计数器会减一;当计数器到达零时,等待的线程将被唤醒,从而实现线程间的通信。

  7. Exchanger
    Exchanger是一个用于两个线程之间交换数据的同步点。两个线程通过exchange()方法交换数据,如果其中一个线程先到达交换点,它会一直等待另一个线程到达才进行交换,从而实现线程间的通信。

  8. Future和Callable
    虽然Future和Callable本身不直接用于线程间通信,但它们可以用于获取异步计算的结果。通过Future的get()方法,一个线程可以等待另一个线程完成计算并获取结果,从而实现间接的线程间通信。

二十五、什么是CAS操作?它在Java并发中有什么应用?

CAS(Compare-And-Swap)操作是一种无锁操作,它通过比较内存中的值与预期值是否相等来实现原子操作,解决并发环境下的数据竞争问题。

  • CAS操作包含三个值:V(内存地址存放的实际值)、O(预期的值,即旧值)和N(更新的新值)。
  • 它的工作原理是,当线程需要使用某个共享变量时,会先将其值(V)与预期值(O)进行比较,如果两者相等,则说明该值没有被其他线程修改过,线程可以将该值更新为新值(N)。这个过程是原子的,即不会被其他线程打断。

在Java中,CAS操作通常通过sun.misc.Unsafe类实现,该类提供了硬件级别的原子操作。由于CAS操作不需要加锁,因此它可以避免加锁操作所带来的性能开销,提高程序的并发性能。

CAS应用场景:

  • 原子性操作:CAS操作可以用于实现原子性操作,如计数器的自增、自减等。由于CAS是一种无锁操作,因此它可以避免使用锁机制所带来的开销,使得原子性操作更加高效。
  • 并发控制:CAS操作可以用于实现乐观锁机制,通过不断尝试更新共享变量的值来实现并发控制。如果更新失败(即预期值与内存中的值不相等),则说明有其他线程正在修改该值,当前线程可以选择重试或放弃操作。
  • 无锁数据结构:CAS操作可以用于实现无锁数据结构,如无锁队列、无锁链表等。这些数据结构通过CAS操作来确保线程安全,避免了使用锁机制所带来的开销和死锁问题。

需要注意的是,虽然CAS操作具有高效性和无阻塞性等优点,但它也存在一些问题和限制。例如,CAS操作只能保证单个共享变量的原子性操作,对于多个共享变量的复合操作则无法保证原子性。此外,CAS操作还存在ABA问题(即一个值被其他线程修改后又改回原来的值,但当前线程并不知道这个变化过程),这可能会导致程序出现错误。

二十六、Java中的原子类有哪些?它们是如何保证原子性的?

Java中的原子类(Atomic Classes)主要用于在高并发的情况下,实现线程安全的操作。这些原子类位于java.util.concurrent.atomic包中,包括AtomicIntegerAtomicLongAtomicBoolean等基本类型的原子类,以及AtomicReference引用类型的原子类。此外,还有AtomicIntegerFieldUpdaterAtomicLongFieldUpdater等原子更新器类,用于对某个类的字段进行原子更新。

  • 原子类保证原子性的主要方式是通过底层使用CAS(Compare-And-Swap)机制。CAS是一种基于硬件支持的原子操作,它包含三个参数:内存地址V、预期的原值A和新值B。当且仅当内存地址V的值等于预期的原值A时,才会将V的值更新为新值B。如果V的值与A不相等,说明已经有其他线程修改了V的值,此时当前线程可以选择重新读取V的值并再次尝试更新,或者选择放弃更新。这种机制可以确保在并发环境下对共享变量的更新是原子性的。

  • 与synchronized关键字和Lock锁相比,原子类在粒度上更细,可以把竞争范围缩小到变量级别,从而获得更细粒度的并发控制。此外,原子类通常比使用锁的效率更高,除了在高度竞争的情况下。这是因为CAS操作是一种无锁操作,它避免了加锁和解锁的开销,减少了线程间的竞争和阻塞。

  • 需要注意的是,虽然原子类提供了线程安全的操作,但在使用时仍需要注意避免ABA问题(即一个值被其他线程修改后又改回原来的值,但当前线程并不知道这个变化过程)和循环时间长开销大等问题。此外,对于多个共享变量的复合操作,仍需要使用锁或其他同步机制来确保原子性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零开始!学习绘制3D表情的详细指南

在2020 年的苹果全球开发者大会(WWDC),苹果发布了新的 macOS 11(又名 Big Sur)。其中在UI视觉方面macOS Big Sur 系统最大的变化就是图标上, Big Sur更新了很多新设计风格的 3D应用图标,3D设计的确可以提升UI整体的视觉氛围,并且现…

Linux——socket编程之tcp通信

前言 前面我们学习socket的udp通信,了解到了socket的概念与udp的实现方法,今天我们来学习一下面向连接的tcp通信。 一、tcp套接字创建 UDP和TCP都是通过套接字(socket)来实现通信的,因此TCP也得使用socket()接口创建…

企业架构领域的天花板——TOGAF证书

TOGAF证书是一项重要的企业架构认证,为专业人士提供了广泛的知识和技能,帮助他们在企业架构领域取得突破。 一、TOGAF证书的重要 提升专业知识和技能:TOGAF证书提供了广泛的企业架构知识和最佳实践,使专业人士能够更好地理解和应…

ESD静电问题 | 电容谐振频率点及选择

静电枪放出的静电属于共模干扰 针对静电整改加电容的时候选择频率在17.5MHz~350MHz区间的电容 【转自微信公众号:韬略科技EMC】

漫画对话 ai翻译

復讐の教科書ーー81 81-1 いい加減吐け!!冴木!! 快说吧!!冴木!! お前が一連の事件の犯人なんだろ!? 你就是连续事件的犯人吧!? だか…

elementUI表格table文字不换行

在对应不需要换行的列加上属性::show-overflow-tooltip"true" 即可

leetcode 1235

leetcode 1235 代码 class Solution { public:int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {int n startTime.size();vector<vector<int>> jobs(n);for(int i0; i<n; i){jobs[i] …

CMakeLists.txt语法规则:数学运算 math

一. 简介 前面几篇文章学习了 CMakeLists.txt语法中的一些常用变量&#xff0c;常用命令&#xff0c;双引号的作用。条件判断语句&#xff0c;循环语句等等。 本文简单学习一下 CMakeLists.txt语法中数学运算 match。 二. CMakeLists.txt语法规则&#xff1a;数学运算 math 在…

【动态规划】子数组、子串系列I|最大子数组和|环形子数组的最大和|乘积最大子数组|乘积为正数的最长子数组长度

一、最大子数组和 最大子数组和 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.返回值为dp表每个位置的最大值&#xff0c;而不是只看最后一个位置&#xff0c;因为可能最后一个位置都不选 2.可以直接在填dp表的时候就进行返回值的比较 3.如果初始化选择多开一个位…

Apifox 教程:如何实现跨语言调用(Java、PHP、Python、Go 等)

在一些特定场景下&#xff0c;比如需要在 Apifox 中对文件进行读写、加密、转换格式或者进行其它业务的操作时&#xff0c;仅使用 Apifox 内置的 JS 类库可能无法满足业务需求&#xff0c;这时&#xff0c;就可以借助「外部程序」作为解决方案。 外部程序是保存在「外部程序目…

Tower for Mac:Git管理的新境界

Tower for Mac&#xff0c;让您的Git管理进入新境界&#xff01;这款专为Mac用户打造的Git客户端&#xff0c;凭借其出色的性能和丰富的功能&#xff0c;成为众多开发者的首选工具。 Tower不仅支持常规的Git操作&#xff0c;如提交、推送和拉取&#xff0c;还提供了许多高级功能…

AR人脸美妆SDK解决方案,让妆容更加贴合个人风格

美妆行业正迎来前所未有的变革&#xff0c;为满足企业对高效、精准、创新的美妆技术需求&#xff0c;美摄科技倾力打造了一款企业级AR人脸美妆SDK解决方案&#xff0c;为企业打开美妆领域的新世界大门。 革命性的人脸美妆技术 美摄科技的AR人脸美妆SDK解决方案&#xff0c;不…

攻略:ChatGPT3.5~4.0(中文版)国内无限制免费版(附网址)【2024年5月最新更新】

一、什么是ChatGPT&#xff1f; 1、ChatGPT的全名是Chat Generative Pre-trained Transformer&#xff0c;其中"chat"表示聊天。"GPT"则是由三部分组成&#xff1a;生成式&#xff08;generative&#xff09;意味着具有创造力&#xff1b;预训练&#xff0…

计算机毕业设计 | vue+springboot图书借阅 书籍管理系统(附源码)

1. 开发目的 实现图书的智能化、信息化和简单化&#xff1b;实现图书信息的增加、删除、修改、查找、借阅、还书、收藏的显示操作及实时数据库的提交和更改和对普通用户的增、删、改、查&#xff1b;提高图书管理员工作信息报送及反馈的工作效率&#xff0c;减轻管理员的劳动负…

基于Spring Boot框架实现大学生选课管理系统

文章目录 源代码下载地址项目介绍项目功能界面预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 项目功能 教务处管理 开课、开班审批&#xff0c;排课处理&#xff0c;班级操作&#xff0c;选课时间段管理** 使用了sql解决了开课开班的时间段的冲突…

Python的Web框架Flask+Vue生成漂亮的词云图

生成效果图 输入待生成词云图的文本&#xff0c;点击生成词云即可&#xff0c;在词云图生成之后&#xff0c;可以点击下载图片保存词云图。 运行步骤 分别用前端和后端编译器&#xff0c;打开backend和frontend文件夹。前端运行 npm install &#xff0c;安装相应的包。后端…

再也不用担心 AI 图片脸崩手崩了

如果你经常用 Stable Diffusion 画人物&#xff0c;相信你一定画出过脸崩的图片。这也是目前文生图 AI 工具普遍存在的问题。连 Midjourney V6 也不例外&#xff01;当它画一个人的时候表现还好&#xff0c;当画面里的人一多&#xff0c;局面就难以控制了。 看&#xff0c;这就…

远动通讯屏的作用

远动通讯屏的作用 远动通讯屏有时有称为调度数据网柜&#xff0c;远动通讯屏具体干啥作用&#xff1f;远动通讯屏是以计算机为基础的生产过程与调度自动化系统&#xff0c;可以对现场的运行设备进行监视和控制、以实现数据采集、设备测量、参数调节以及各类信号报警等各项功能。…

segment anythin 新标注工具 paddleocr训练自己的数据

快递单ocr检测 1.总结2.需求3.方案4.面单定位4.1反转图片扩充数据集4.2新的标注方式4.3json2yolo4.4yolov5推理 5.paddleocr5.1 数据标注5.2 文本检测训练5.3 文本识别训练检测结果 1.总结 按照惯例&#xff0c;先吐槽一下。反正也没人看我比比歪歪。做事全部藏着掖着&#xf…

致力于双碳减排服务——安科瑞推出碳电表

1. 概述 全球首个“碳关税”——欧盟碳边境调节机制于2023年10月启动试运行。自此&#xff0c;首批纳入欧盟碳边境调节机制的6个行业相关产品在出口至欧盟国家时需提供碳排放数据&#xff0c;这会倒逼国内制造业企业加快开展产品碳足迹核查的步伐。以钢铁行业为例&#xff0c;…
最新文章