1. 继承 Thread 类和实现 Runnable 接口有什么区别?

从本质上讲,继承 Thread 类是将线程本身和要执行的任务合二为一。而实现 Runnable 接口则是将线程(Thread)和要执行的任务(Runnable)分离开来。

具体区别如下:

第一,Java 语言的设计是单继承的。如果一个类已经继承了 Thread 类,就无法再继承其他类,这大大限制了类的扩展性。而实现 Runnable 接口则没有这个问题,类可以同时实现多个接口,并且还可以继承另一个父类,灵活性更高。

第二,实现 Runnable 接口更符合面向对象的思想。它将“任务”本身定义为一个独立的对象(Runnable 对象),而不是一个“线程”。这个任务对象可以被多个线程共享和执行,非常适合处理多个线程处理同一份资源的情况。而继承 Thread 类的方式,线程和任务强绑定,不利于共享资源。

第三,从代码架构上看,实现 Runnable 接口使得代码更加清晰。线程负责控制和管理线程生命周期,而 Runnable 对象只负责定义具体的执行逻辑,职责分离,降低了代码的耦合度。

第四,在线程池等高级并发工具中,它们通常接受的是 Runnable 或 Callable 任务对象,而不是一个 Thread 对象。因此,实现 Runnable 接口使得任务可以直接提交给线程池执行,而继承 Thread 类的方式则与线程池的配合不如前者自然。

总结来说,实现 Runnable 接口通常是更被推荐的做法,因为它更灵活、更符合面向对象设计原则,并且能与 Java 并发API更好地协作。只有在需要重写或增强 Thread 类本身的方法时,才考虑使用继承 Thread 类的方式。

2. Future 和 FutureTask 的作用是什么?

Future 的作用

Future 是一个接口,它代表了异步计算的结果。你可以把它想象成一张“提货单”。当你把一件需要长时间处理的任务(比如一个复杂的计算、一个网络请求、一个数据库查询)交给另一个线程(比如通过线程池)去执行时,你并不会立刻得到结果。相反,你会立即得到一个 Future 对象。

这张“提货单”提供了几个核心方法,让你在未来某个时候去“提取”结果或检查任务的状态:

  1. get(): 这是一个关键方法,用于获取异步计算的结果。如果计算还没完成,调用 get() 方法的线程会被阻塞,直到计算完成并返回结果。你也可以使用带超时参数的 get(long timeout, TimeUnit unit) 来避免无限期等待。

  2. isDone(): 用于查询异步任务是否已经执行完毕(无论是正常完成、被取消还是抛出异常,都返回 true)。

  3. cancel(boolean mayInterruptIfRunning): 尝试取消这个异步任务的执行。

  4. isCancelled(): 判断任务是否在正常完成之前被取消了。

FutureTask 的作用

FutureTask 是 Future 接口的一个极其重要且基础的实现类。它本身既是一个 Future,又是一个 Runnable。这意味着:

  1. 因为它实现了 Runnable 接口,所以它可以被提交给一个 Thread 去执行,或者交给 ExecutorService(线程池)去执行。

  2. 因为它实现了 Future 接口,所以它在被提交执行后,会返回一个 Future 对象(实际上就是它自己),让你能够通过 Future 的方法来管理和获取任务执行的结果。

两者的关系和区别

Future 是一个定义规则的接口,它规定了如何与一个异步任务的结果进行交互。而 FutureTask 是这个接口的一个具体实现,它是一个实实在在的、可被调度执行的“任务单元”。

工作流程示例

  1. 你创建一个 CallableRunnable 对象,里面封装了需要异步执行的逻辑。

  2. 你用这个 Callable/Runnable 对象作为参数,创建一个 FutureTask 对象。

  3. 你将这个 FutureTask 对象(因为它是一个 Runnable)交给一个线程(new Thread(futureTask).start())或线程池(executor.submit(futureTask))去执行。

  4. 主线程可以继续做其他事情,而不用等待任务完成。

  5. 当主线程需要任务的结果时,它调用 futureTask.get() 方法。如果任务已完成,结果立刻返回;如果未完成,主线程则阻塞等待,直到任务完成。

总结核心作用

  • Future 的作用:提供一个标准化的、非阻塞的接口,用于访问异步计算的结果。它将任务的提交和执行与结果的获取解耦,是实现并发编程中“异步调用”模式的关键。

  • FutureTask 的作用:作为 Future 和 Runnable 的结合体,它是一个可以被线程执行的具体任务,并且自身就持有执行后的结果。它是将异步计算逻辑(Callable/Runnable)包装成一个可管理、可获取结果的任务对象的最常用工具。

3. 什么是线程安全?如何保证线程安全?

一、什么是线程安全?

线程安全是一个核心的编程概念,尤其在多线程并发环境中至关重要。简单来说,线程安全指的是当多个线程同时访问某个类、对象或方法时,这个类、对象或方法总能表现出正确的行为,而不需要额外的同步或协调操作。

我们可以从以下几个关键点来理解“正确的行为”:

  1. 数据的原子性(Atomicity):操作要么全部完成,要么完全不发生,不会出现中间状态。例如,一个计数器i++操作,在非线程安全情况下,可能因为多个线程同时读写而导致计数错误。

  2. 数据的一致性(Consistency):多线程的访问不会破坏数据的内在约束、前后关系和各种状态的一致性。例如,一个双向链表,在修改其中一个节点的同时,必须同步修改其前后节点的指针,否则链表结构就会被破坏。

  3. 执行的顺序性(Ordering):在某些情况下,代码的执行顺序至关重要。编译器和处理器为了优化性能可能会进行指令重排,但在多线程环境下,不恰当的重排可能导致其他线程看到错误的数据状态。

一个非线程安全的典型例子是一个普通的计数器。如果多个线程同时执行count++(它包含读取、加1、写入三个步骤),最终结果很可能小于实际应累加的次数,因为多个线程可能读到同一个旧值。

一个线程安全的例子是StringBuffer类。它的关键方法(如append())都使用了synchronized关键字进行修饰,因此多个线程同时调用这些方法也不会导致其内部状态错乱。

二、如何保证线程安全?

保证线程安全的核心思想是对共享资源的访问进行“序列化”或“隔离”,使得在同一时刻最多只有一个线程可以操作该资源。以下是几种常见的方法,从简单到复杂:

1. 不可变对象(Immutable Objects)这是最简单也是最有效的方法。如果一个对象在创建后其状态就不能被修改(即所有字段都是final的,并且没有提供任何修改状态的方法),那么它天生就是线程安全的。因为线程只能读取它,无法改变它,所以不存在竞争条件。例如,String类就是一个不可变对象。

2. 线程封闭(Thread Confinement)不共享数据自然就没有安全问题。将数据限制在单个线程内使用,其他线程无法访问。例如:

  • 局部变量:每个线程都有自己独立的栈空间,局部变量是线程私有的。

  • ThreadLocal 类:这个类可以为每个线程创建一个变量的独立副本,每个线程只操作自己的副本。

3. 同步(Synchronization)这是最直接的控制线程访问的手段,通过锁机制实现。

  • synchronized 关键字:可以修饰代码块或方法。它确保在同一时刻,只有一个线程可以执行某个代码块或方法。进入同步代码前需要获得内置锁(监视器锁),执行完毕后释放锁。

  • ReentrantLock 类:这是java.util.concurrent.locks包下的一个显式锁,提供了比synchronized更灵活的锁操作,如可定时、可轮询、可中断的锁请求,以及公平锁等高级功能。

4. 原子变量(Atomic Variables)java.util.concurrent.atomic包提供了一系列原子类(如AtomicInteger, AtomicLong, AtomicReference等)。它们通过硬件级别的CAS(Compare-And-Swap)指令来实现单个变量的原子操作(如getAndIncrement, compareAndSet)。这种方式比同步锁的性能开销小很多,非常适合做计数器、状态标志等。

5. 线程安全的数据结构(Thread-Safe Collections)直接使用为并发设计的数据结构,避免自己实现复杂的同步逻辑。

  • 并发集合类java.util.concurrent包提供了高性能的线程安全集合,如ConcurrentHashMap(替代Hashtable和同步的HashMap)、CopyOnWriteArrayList(读多写少场景)、ConcurrentLinkedQueue等。

  • 使用Collections工具类包装:可以通过Collections.synchronizedMap(new HashMap())等方式来创建同步的包装器集合,但这种方式性能通常不如并发集合类。

总结一下:

选择哪种方式取决于具体场景。优先考虑不可变对象线程封闭,从根本上避免问题。如果必须共享数据,优先考虑使用原子变量线程安全的数据结构,它们的性能通常更好。最后,如果逻辑非常复杂,上述方法都无法满足,再使用同步(synchronized或Lock) 来保护临界区代码,但要注意控制锁的粒度,避免性能瓶颈。

4. synchronized 关键字的作用是什么?

synchronized 是 Java 语言中用于实现线程同步的关键字,它的核心作用是解决多线程环境下访问共享资源时可能出现的数据不一致线程干扰问题。它提供了一种内置的锁机制,以确保同一时刻只有一个线程可以执行某个特定的代码段或方法。

具体来说,它的作用主要体现在以下几个方面:

  1. 保证原子性(Atomicity)原子性是指一个操作或多个操作要么全部执行完成,要么都不执行,不会被打断。synchronized 可以确保被它修饰的代码块或方法作为一个不可分割的单元执行。例如,一个经典的“读-改-写”操作(如 i++),如果没有同步保护,多个线程同时执行会导致最终结果错误。使用 synchronized 后,这个操作就变成了原子的。

  2. 保证可见性(Visibility)可见性是指当一个线程修改了共享变量的值后,其他线程能够立即看到这个修改。Java 内存模型(JMM)中,每个线程有自己的工作内存,对变量的操作可能暂时保存在工作内存中,而不是立即写回主内存。synchronized 关键字在释放锁之前,会强制将工作内存中的修改刷新到主内存中;而在获取锁之后,会清空工作内存,从主内存重新加载变量值。这样就保证了线程每次都能看到共享资源的最新状态。

  3. 保证有序性(Ordering)有序性是指程序执行的顺序按照代码的先后顺序。为了提高性能,编译器和处理器可能会对指令进行重排序。synchronized 限制了一定的有序性,它确保在同步代码块内部,虽然指令可能被重排,但重排后的结果必须与代码顺序执行的结果一致。并且,一个线程在解锁前的所有操作对于后来获得锁的线程来说,是可见且有序的。

synchronized 的三种主要使用方式:

  • 同步实例方法:锁是当前对象实例(this)。

    public synchronized void method() {
        // 同步代码
    }
  • 同步静态方法:锁是当前类的 Class 对象。

    public static synchronized void staticMethod() {
        // 同步代码
    }
  • 同步代码块:可以指定任意对象作为锁,提供了更灵活的同步控制。

    public void method() {
        // 非同步代码...
    
        synchronized (lockObject) { // 使用 lockObject 作为锁
            // 同步代码
        }
    
        // 非同步代码...
    }

总结一下:synchronized 关键字通过内置锁(也称为监视器锁)的机制,强制要求线程在访问被保护的代码或数据前必须先获得锁,访问完成后释放锁。这种互斥特性确保了在同一时间只有一个线程能执行临界区代码,从而有效地解决了多线程并发访问带来的数据竞争问题,保证了线程安全。它是 Java 中最基本、最常用的线程同步工具之一。

5. synchronized 方法和 synchronized 代码块的区别?

1. 锁的粒度不同synchronized 方法锁定的是整个对象实例(对于实例方法)或整个类对象(对于静态方法),锁的粒度较粗。synchronized 代码块可以精确指定需要加锁的对象(可以是任意对象实例或类对象),因此能够实现更细粒度的锁控制,有助于提高并发性能。

2. 灵活性不同synchronized 方法必须对整个方法体加锁,缺乏灵活性。synchronized 代码块可以只对方法中需要同步的关键部分加锁,其余代码仍可异步执行,灵活性更高。

3. 性能影响不同由于 synchronized 方法锁范围更大且更不灵活,在高并发场景下更容易成为性能瓶颈。synchronized 代码块通过减小锁的范围和持续时间,通常能获得更好的性能表现。

4. 锁对象选择不同synchronized 方法隐式使用当前对象(this)或类对象作为锁。synchronized 代码块可以显式指定任意对象作为锁,提供了更多选择来控制同步策略。

5. 代码控制方式不同synchronized 方法是通过方法声明中的关键字实现同步。synchronized 代码块是通过在方法体内定义同步块来实现同步,允许更精确地控制同步边界。

6. 可读性差异synchronized 方法同步意图明确但同步范围不够清晰。synchronized 代码块能明确展示同步的具体代码段,但需要更多代码实现。

总体而言,synchronized 代码块相比 synchronized 方法提供了更精细的锁控制、更好的灵活性和潜在的性能优势,但实现稍复杂。synchronized 方法实现简单但灵活性较差。在实际开发中,通常推荐优先使用 synchronized 代码块来实现同步。

6. 什么是重入锁(ReentrantLock)?

核心概念:重入锁(ReentrantLock)是 Java 并发编程中 java.util.concurrent.locks 包下的一个类,它实现了 Lock 接口。它是一种与 synchronized 关键字功能类似,但更强大、更灵活的线程同步机制。其最核心的特性是“重入性”。

1. 什么是“重入性”(Reentrancy)?“重入性”是指同一个线程可以多次获取同一把锁。当一个线程已经持有某个重入锁时,它可以再次成功请求(lock)这个锁,而不会被自己已经持有的锁阻塞住。锁内部会维护一个持有计数(hold count),每次成功的 lock 操作都会使计数加 1,而每次 unlock 操作会使计数减 1。只有当计数减到 0 时,锁才会被真正释放,其他线程才能获取它。

  • synchronized 也是可重入的:这一点很重要,内置锁 synchronized 关键字同样具有重入性。重入锁(ReentrantLock)在这个基本行为上与 synchronized 是一致的。

2. 与 synchronized 相比的主要优势(为什么需要它):尽管基本功能相似,但 ReentrantLock 提供了更多高级功能,使得它在某些场景下比 synchronized 更强大、更灵活。

  • 尝试非阻塞地获取锁:提供了 tryLock() 方法。线程可以尝试获取锁,如果锁在那一刻不可用,它可以选择立即返回失败或等待一段指定时间,而不是像 synchronized 那样必须一直阻塞等待。这有助于避免死锁和更灵活地控制程序流程。

  • 可中断的锁获取:提供了 lockInterruptibly() 方法。当线程在等待获取锁的过程中,可以被其他线程中断(调用 interrupt() 方法),并响应中断抛出 InterruptedException。而使用 synchronized 时,一个等待锁的线程是无法被中断的。

  • 公平锁选项:在创建 ReentrantLock 对象时,可以传入一个 fairness(公平性)参数。如果设置为 true,它就成为一个公平锁。公平锁会保证锁的分配按照线程请求的先后顺序(即等待时间最长的线程优先获得锁),这可以避免某些线程“饿死”(永远拿不到锁)。synchronized 关键字提供的锁是非公平的。

  • 绑定多个条件:一个 ReentrantLock 对象可以同时绑定多个 Condition(条件变量)对象。这意味着你可以对同一把锁下的不同线程组进行更精细的等待/通知控制。而 synchronized 关键字只能有一个内置的等待集(wait set),通过 wait(), notify(), notifyAll() 操作。

3. 基本用法:使用 ReentrantLock 必须显式地进行加锁和解锁操作,并且通常将解锁操作放在 finally 块中以确保锁一定会被释放,防止发生异常导致锁无法释放。

一个典型的使用模式如下:

ReentrantLock lock = new ReentrantLock(); // 默认创建非公平锁
// ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁

lock.lock(); // 获取锁
try {
    // ... 访问或修改共享资源,执行临界区代码
} finally {
    lock.unlock(); // 必须在finally块中释放锁
}

总结:重入锁(ReentrantLock)是一个提供了与 synchronized 相同重入特性,但功能更丰富、控制更精细的显式锁实现。它在需要尝试获取锁、可中断锁、公平锁或复杂条件等待等高级同步功能时,是 synchronized 一个非常强大的替代品。然而,由于其需要手动编码加锁和解锁,代码稍显复杂,因此在不需要这些高级功能的简单场景下,使用 synchronized 因其简洁性和自动释放锁的特性仍然是很好的选择。

7. ReentrantLock 与 synchronized 的区别?

ReentrantLock 与 synchronized 的区别主要体现在以下几个方面:

第一,从所属层级来看,synchronized 是 Java 语言层面的关键字,由 JVM 原生提供并实现。而 ReentrantLock 是 JDK 1.5 之后提供的 API 层面的锁,具体位于 java.util.concurrent.locks 包下,它是一个类。

第二,在锁的获取与释放方式上,synchronized 的加锁与解锁过程是隐式的。线程进入 synchronized 修饰的代码块会自动获取锁,退出代码块(无论是正常退出还是异常退出)时 JVM 会自动释放锁,无需开发者手动干预。ReentrantLock 则要求开发者必须显式地进行加锁(lock() 方法)和解锁(unlock() 方法)操作,并且通常将解锁操作放在 finally 代码块中以确保锁一定能被释放,避免死锁。

第三,在等待可中断方面,synchronized 的等待是不可中断的。如果一个线程试图获取一个已经被占用的锁,它会被阻塞并一直等待下去,期间无法响应中断。ReentrantLock 提供了中断等待的机制,通过使用 lockInterruptibly() 方法,等待锁的线程可以响应中断信号,从而提前结束等待状态。

第四,在公平锁的实现上,synchronized 的锁获取策略是非公平的,它不保证等待时间最长的线程优先获得锁,这可能导致某些线程长期等待(“饥饿”)。ReentrantLock 提供了构造公平锁和非公平锁的灵活性。通过在构造函数中传入 true 参数,可以创建一个公平锁,它按照线程请求锁的顺序(即等待时间)来分配锁,保证了公平性。

第五,在绑定多个条件(Condition)的能力上,synchronized 通过 wait()、notify() 或 notifyAll() 方法来实现线程间的等待/通知机制,但整个锁对象只能有一个等待集合(wait set),所有线程都使用同一个条件。ReentrantLock 可以绑定多个 Condition(条件队列)对象,通过多次调用 newCondition() 方法实现。这意味着我们可以将线程分组,在不同的条件下进行等待和唤醒,从而实现更精细的线程间通信和控制。

第六,从性能方面来看,在早期版本中,ReentrantLock 的性能通常优于 synchronized。但随着 JDK 版本的迭代,尤其是 synchronized 引入了偏向锁、轻量级锁(自旋锁)等优化后,两者的性能差距已经变得很小。因此,现在性能通常不再是选择二者的主要因素。

总结来说,synchronized 的使用更加简单便捷,由 JVM 负责管理,不易出错。而 ReentrantLock 则提供了更丰富的功能、更高的灵活性和对中断的响应能力,但需要开发者手动控制锁的释放,编码稍复杂。在选择时,如果需要使用高级功能(如可中断、公平锁、多个条件),则选择 ReentrantLock;否则,优先考虑使用简单可靠的 synchronized。

8. 如何使用 ReentrantLock 实现公平锁?

1. 公平锁的概念公平锁的核心思想是保证线程获取锁的顺序与其请求锁的顺序一致,即先到先得。这可以防止线程饥饿现象。

2. ReentrantLock 的公平锁实现在创建 ReentrantLock 对象时,通过构造函数传入 true 参数即可创建公平锁:

ReentrantLock fairLock = new ReentrantLock(true);

3. 关键特性说明

  • 公平锁通过维护一个有序队列实现线程调度

  • 相比非公平锁,公平锁的吞吐量略低(约10%)

  • 能有效避免线程饥饿问题

  • 锁的公平性仅适用于锁的获取操作,不保证线程调度的公平性

4. 完整使用示例

import java.util.concurrent.locks.ReentrantLock;

public class FairLockExample {
    // 创建公平锁实例
    private static final ReentrantLock fairLock = new ReentrantLock(true);
    
    public static void main(String[] args) {
        // 创建多个线程测试
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Worker(), "Thread-" + (i + 1));
            thread.start();
        }
    }
    
    static class Worker implements Runnable {
        @Override
        public void run() {
            fairLock.lock();  // 获取公平锁
            try {
                System.out.println(Thread.currentThread().getName() + " 获得锁");
                // 模拟业务处理
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                fairLock.unlock();  // 释放锁
            }
        }
    }
}

5. 注意事项

  • 必须在 finally 块中释放锁,确保异常时也能正常释放

  • 公平锁的性能通常低于非公平锁,仅在确实需要公平性时使用

  • 公平锁不能解决所有的公平性问题,只能保证锁获取的公平性

6. 输出特征运行上述代码时,通常会观察到线程基本按照启动顺序依次获得锁,体现了公平锁的先到先得特性。

通过这种方式,你可以简单地使用 ReentrantLock 实现公平锁机制,确保线程获取锁的公平性。

9. 什么是读写锁(ReadWriteLock)?

读写锁是一种线程同步机制,它针对特定的资源访问场景进行了优化,允许多个线程同时读取某个资源,但在写入时则要求独占访问。其核心思想是将对资源的访问分为两类:读访问和写访问,并据此制定了不同的锁规则。

读写锁的关键特性在于它由两把锁构成:一把读锁和一把写锁。其访问约束规则可以概括为:

1. 共享读(Shared Read): 当没有线程持有写锁时,任意数量的线程可以同时获取读锁并访问资源。这意味着读操作是并发的,极大地提高了性能,特别是在读多写少的场景中。

2. 独占写(Exclusive Write): 写锁是排他的。当一个线程成功获取了写锁后,其他任何线程(无论是试图读取还是写入)都无法再获取读锁或写锁,必须等待当前的写锁被释放。这保证了写操作的安全,避免了数据的不一致。

3. 写优先或公平策略(可选): 许多读写锁的实现还提供了策略来选择在读写锁等待时,是优先分配给写者(避免写线程饥饿)还是按照请求的先后顺序(公平策略)来分配。

读写锁的典型工作流程是:

  • 读取数据时: 线程首先获取读锁。获取成功后,它可以与其他持有读锁的线程一同读取数据。读取完成后,释放读锁。

  • 写入数据时: 线程必须获取写锁。在获取写锁的过程中,它会阻塞直到所有先前持有的读锁和写锁都被释放。一旦成功获取,它就独占资源进行写入操作。写入完成后,释放写锁。

使用读写锁的主要优势在于它显著提升了程序的性能。 在传统的互斥锁(如 synchronizedReentrantLock)下,无论读写,同一时刻只允许一个线程访问资源。而在读操作远多于写操作的应用中(例如缓存系统、资源配置文件读取),读写锁允许多个读线程并行,从而大大提高了吞吐量。

总结来说,读写锁是一种高效的并发控制工具,它通过区分读操作和写操作,在保证数据一致性的前提下,最大限度地提高了读操作的并发性。

10. ReadWriteLock 的优势是什么?

ReadWriteLock 的核心优势在于它通过将锁的访问策略细分为读锁和写锁,从而在高并发读多写少的场景下,极大地提升了程序的性能和高并发能力。

具体来说,其优势主要体现在以下几个方面:

第一,它显著提升了读操作的并发性。这是它最核心、最重要的优势。普通的互斥锁(如 synchronized)在任何时刻都只允许一个线程访问共享资源。而 ReadWriteLock 允许多个读线程同时持有读锁,并行地读取数据。只要没有线程在写入,理论上所有的读线程都可以同时进行,这使得系统的吞吐量得到巨大提升。

第二,它保证了写操作的安全性与可见性。写锁是排他的,当一个线程持有写锁时,其他所有线程(无论是读还是写)都无法获取锁,必须等待。这确保了写操作不会被干扰,避免了数据不一致的问题。同时,写锁的释放与后续读锁的获取之间建立了“happens-before”关系,保证了写操作的结果对之后的所有读操作都是可见的。

第三,它有效避免了“读-写”和“写-写”冲突。它通过锁分离的策略,只在线程试图写入时才会阻塞其他所有的读写线程;而在只有读操作时,则完全不会阻塞。这种设计精准地解决了并发编程中最常见的两种冲突,使得线程在大部分时间(读操作时)可以并行执行,只在必要时(写操作时)才进行串行等待。

第四,它相较于简单的互斥锁,在特定场景下能带来更好的性能。这个优势是前几个优势带来的必然结果。在读操作远多于写操作的典型应用场景中(如缓存系统、资源配置文件读取等),使用 ReadWriteLock 可以避免读操作不必要的相互等待,从而大幅减少线程阻塞的时间,提高CPU利用率和系统整体响应速度。

总而言之,ReadWriteLock 的优势在于它聪明地区分了访问类型,通过“读读共享、写写互斥、读写互斥”的规则,在绝对保证数据一致性的前提下,最大限度地提高了读操作的并发度,是针对“读多写少”并发场景的一种高效解决方案。

张追梦
2
加入复习