JAVA中线程池的详解

news2025/1/10 20:55:10

1.概念

顾名思义,线程池就是管理一系列线程的资源池,其提供了一种限制和管理线程资源的方式。每个线程池还维护一些基本统计信息,例如已完成任务的数量。

这里借用《Java 并发编程的艺术》书中的部分内容来总结一下使用线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池一般用于执行多个不相关联的耗时任务,没有多线程的情况下,任务顺序执行,使用了线程池的话可让多个不相关联的任务同时执行。

2.线程池的构造        

ThreadPoolExecutor是线程池的核心实现类,在JDK1.5引入,位于java.util.concurrent包,由Doug Lea完成。

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
  7. ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。 

Executor接口

public interface Executor {
    // 该接口中只定义了一个Runnable作为入参的execute方法
    void execute(Runnable command);
}

查看Executor接口的实现类图

  • Executor线程池相关顶级接口,它将任务的提交与任务的执行分离开来
  • ExecutorService继承并扩展了Executor接口,提供了Runnable、FutureTask等主要线程实现接口扩展
  • ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务
  • ScheduledExecutorService继承ExecutorService接口,并定义延迟或定期执行的方法
  • ScheduledThreadPoolExecutor继承ThreadPoolExecutor并实现了ScheduledExecutorService接口,是延时执行类任务的主要实现

 1、newCachedThreadPool

创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个可缓存的线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        // 提交任务到线程池
        for (int i = 0; i < 5; i++) {
            final int taskNumber = i;
            cachedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务: " + taskNumber + " 由线程 " + Thread.currentThread().getName() + " 处理");
                    try {
                        // 模拟任务执行时间
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        // 关闭线程池,不接受新任务,已提交的任务继续执行
        cachedThreadPool.shutdown();

        // 等待所有任务完成
        while (!cachedThreadPool.isTerminated()) {
            // 可以在此处做其他工作,或者只是等待
        }

        System.out.println("所有任务已完成");
    }
}

运行结果:

执行任务: 1 由线程 pool-1-thread-2 处理
执行任务: 3 由线程 pool-1-thread-4 处理
执行任务: 2 由线程 pool-1-thread-3 处理
执行任务: 4 由线程 pool-1-thread-5 处理
执行任务: 0 由线程 pool-1-thread-1 处理
所有任务已完成

在这个例子中,我们创建了一个可缓存的线程池,并提交了5个任务。每个任务简单地打印出自己的任务编号和执行它的线程名称,然后休眠1秒钟来模拟工作负载。

在提交所有任务后,我们调用`shutdown()`方法来关闭线程池。这将导致线程池不再接受新任务,但是已经提交的任务会继续执行直到完成。然后我们使用一个循环来等待所有任务完成。当所有任务都执行完毕,线程池中的线程如果没有新的任务将会被回收。

2、newFixedThreadPool

创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小为5的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

        // 提交任务到线程池
        for (int i = 0; i < 10; i++) {
            final int taskNumber = i;
            fixedThreadPool.submit(() -> {
                System.out.println("执行任务: " + taskNumber + " 由线程 " + Thread.currentThread().getName() + " 处理");
                try {
                    // 模拟任务执行时间
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        // 关闭线程池,不接受新任务,已提交的任务继续执行
        fixedThreadPool.shutdown();

        // 等待所有任务完成
        while (!fixedThreadPool.isTerminated()) {
            // 可以在此处做其他工作,或者只是等待
        }

        System.out.println("所有任务已完成");
    }
}

运行结果: 

执行任务: 0 由线程 pool-1-thread-1 处理
执行任务: 4 由线程 pool-1-thread-5 处理
执行任务: 3 由线程 pool-1-thread-4 处理
执行任务: 2 由线程 pool-1-thread-3 处理
执行任务: 1 由线程 pool-1-thread-2 处理
执行任务: 5 由线程 pool-1-thread-1 处理
执行任务: 7 由线程 pool-1-thread-2 处理
执行任务: 6 由线程 pool-1-thread-3 处理
执行任务: 8 由线程 pool-1-thread-4 处理
执行任务: 9 由线程 pool-1-thread-5 处理
所有任务已完成

在这个例子中:
- 我们创建了一个固定大小为5的线程池,这意味着最多只有5个线程同时运行。
- 提交了10个任务到线程池,由于线程池的大小限制,最多只有5个任务会同时运行,其余的任务会等待直到有线程空闲出来。
- 使用 `shutdown()` 方法关闭线程池,这表示不再接受新任务,但已提交的任务会继续执行直到完成。
- 使用一个循环等待所有任务完成,这是通过检查 `isTerminated()` 方法实现的,该方法在所有任务执行完毕后返回 `true`。

 3、newScheduledThreadPool

创建一个周期性的线程池,支持定时及周期性执行任务。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个具有固定线程数量的ScheduledThreadPoolExecutor
        ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);

        // 定义一个Runnable任务
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("任务执行时间: " + System.currentTimeMillis());
            }
        };

        // 定时执行任务:延迟2秒后开始执行,之后每隔1秒执行一次
        scheduledExecutor.scheduleAtFixedRate(task, 2, 1, TimeUnit.SECONDS);

        // 定时执行任务:在指定的延迟后执行一次
        scheduledExecutor.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("一次性任务执行时间: " + System.currentTimeMillis());
            }
        }, 5, TimeUnit.SECONDS);

        // 取消定时任务,5秒后执行的任务不会被执行
        // scheduledExecutor.schedule(new Runnable() {...}, 5, TimeUnit.SECONDS).cancel(true);

        // 程序运行一段时间后关闭线程池
        try {
            Thread.sleep(10 * 1000); // 让程序运行10秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        scheduledExecutor.shutdown();
    }
}

 运行结果:

任务执行时间: 1725289960007
任务执行时间: 1725289961008
任务执行时间: 1725289962006
一次性任务执行时间: 1725289963008
任务执行时间: 1725289963008
任务执行时间: 1725289964003
任务执行时间: 1725289965001
任务执行时间: 1725289966000
任务执行时间: 1725289966996
任务执行时间: 1725289967994

在这个例子中,我们创建了一个拥有5个线程的 `ScheduledThreadPoolExecutor`。我们定义了一个简单的 `Runnable` 任务,它会在每次执行时打印当前的时间戳。我们使用 `scheduleAtFixedRate` 方法来周期性地执行这个任务,每隔1秒执行一次,从延迟2秒后开始。我们还使用 `schedule` 方法来安排一个在5秒后只执行一次的任务。

最后,我们在程序运行10秒后关闭线程池,以确保程序能够正常结束。如果需要取消某个定时任务,可以使用 `cancel` 方法。

4、newSingleThreadExecutor

创建一个单线程的线程池,可保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

package Text;

import java.util.concurrent.*;

public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService exector =Executors.newSingleThreadExecutor();
        //提交任务给线程执行
        exector.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务1开始执行");
                try{
                    Thread.sleep(2000);//模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务一执行完毕");
            }
        });
        exector.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2开始执行");
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务2执行完毕");
            }
        });
        exector.shutdown();
    }
}

运行结果:

任务1开始执行
任务一执行完毕
任务2开始执行
任务2执行完毕

在这个例子中,两个任务将按照它们提交的顺序执行。由于是单线程,所以任务2会在任务1完成后才开始执行。

如果你想按照优先级来执行任务,你可以使用 `PriorityBlockingQueue` 作为线程池的队列,或者在提交任务时使用 `Callable` 接口和 `Future` 类来设置优先级。不过,`newSingleThreadExecutor` 默认不支持优先级队列,它只是简单地按照任务提交的顺序来执行。如果你需要优先级队列,你可能需要自定义线程池或者使用 `PriorityBlockingQueue` 作为队列来创建线程池。

 二、通过ThreadPoolExecutor类自定义。

  ThreadPoolExecutor类提供了4种构造方法,可根据需要来自定义一个线程池。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        // 省略...
    }

1、共7个参数如下:

(1)corePoolSize:核心线程数,线程池中始终存活的线程数。

(2)maximumPoolSize: 最大线程数,线程池中允许的最大线程数。

(3)keepAliveTime: 存活时间,线程没有任务执行时最多保持多久时间会终止。

(4)unit: 单位,参数keepAliveTime的时间单位,7种可选。

(5)workQueue: 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7种可选。

较常用的是LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。

(6)threadFactory: 线程工厂,主要用来创建线程,默及正常优先级、非守护线程。

(7)handler:拒绝策略,拒绝处理任务时的策略,4种可选,默认为AbortPolicy。

  3.线程池的运行原理

说完线程池的核心构造参数,接下来来讲解这些参数在线程池中是如何工作的。

线程池刚创建出来是什么样子呢,如下图:

 没错,刚创建出来的线程池中只有一个构造时传入的阻塞队列,里面并没有线程,如果想要在执行之前创建好核心线程数,可以调用 prestartAllCoreThreads 方法来实现,默认是没有线程的。

 当有线程通过 execute 方法提交了一个任务,会发生什么呢?

首先会去判断当前线程池的线程数是否小于核心线程数,也就是线程池构造时传入的参数 corePoolSize。

如果小于,那么就直接通过 ThreadFactory 创建一个线程来执行这个任务,如图

当任务执行完之后,线程不会退出,而是会去阻塞队列中获取任务,如下图 

接下来如果又提交了一个任务,也会按照上述的步骤去判断是否小于核心线程数,如果小于,还是会创建线程来执行任务,执行完之后也会从阻塞队列中获取任务。

这里有个细节,就是提交任务的时候,就算有线程池里的线程从阻塞队列中获取不到任务,如果线程池里的线程数还是小于核心线程数,那么依然会继续创建线程,而不是复用已有的线程。

如果线程池里的线程数不再小于核心线程数呢?那么此时就会尝试将任务放入阻塞队列中,入队成功之后,如图

这样,阻塞的线程就可以获取到任务了。

但是,随着任务越来越多,队列已经满了,任务放入失败,怎么办呢?

此时会判断当前线程池里的线程数是否小于最大线程数,也就是入参时的 maximumPoolSize 参数

如果小于最大线程数,那么也会创建非核心线程来执行提交的任务,如图

所以,就算队列中有任务,新创建的线程还是会优先处理这个提交的任务,而不是从队列中获取已有的任务执行,从这可以看出,先提交的任务不一定先执行

假如线程数已经达到最大线程数量,怎么办呢?

此时就会执行拒绝策略,也就是构造线程池的时候,传入的 RejectedExecutionHandler 对象,来处理这个任务。

JDK 自带的 RejectedExecutionHandler 实现有 4 种

  • AbortPolicy:丢弃任务,抛出运行时异常
  • CallerRunsPolicy:由提交任务的线程来执行任务
  • DiscardPolicy:丢弃这个任务,但是不抛异常
  • DiscardOldestPolicy:从队列中剔除最先进入队列的任务,然后再次提交任务

线程池创建的时候,如果不指定拒绝策略就默认是 AbortPolicy 策略。

当然,你也可以自己实现 RejectedExecutionHandler 接口,比如将任务存在数据库或者缓存中,这样就可以从数据库或者缓存中获取被拒绝掉的任务了。

到这里,我们发现,线程池构造的几个参数 corePoolSize、maximumPoolSize、workQueue、threadFactory、handler 我们都在上述的执行过程中讲到了,那么还差两个参数 keepAliveTime 和 unit(unit 是 keepAliveTime 的时间单位)没讲到,所以 keepAliveTime 是如何起作用的呢,这个问题留到后面分析。

说完整个执行的流程,接下来看看 execute 方法的代码是如何实现的。

public void execute(Runnable command) {
    // 首先检查提交的任务是否为null,是的话则抛出NullPointerException。
    if (command == null)
        throw new NullPointerException();

    // 获取线程池的当前状态(ctl是一个AtomicInteger,其中包含了线程池状态和工作线程数)
    int c = ctl.get();

    // 1. 检查当前运行的工作线程数是否少于核心线程数(corePoolSize)
    if (workerCountOf(c) < corePoolSize) {
        // 如果少于核心线程数,尝试添加一个新的工作线程来执行提交的任务
        // addWorker方法会检查线程池状态和工作线程数,并决定是否真的添加新线程
        if (addWorker(command, true))
            return;
        // 重新获取线程池的状态,因为在尝试添加线程的过程中线程池的状态可能已经发生变化
        c = ctl.get();
    }

    // 2. 尝试将任务添加到任务队列中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 双重检查线程池的状态
        if (! isRunning(recheck) && remove(command))  // 如果线程池已经停止,从队列中移除任务
            reject(command);
        // 如果线程池正在运行,但是工作线程数为0,尝试添加一个新的工作线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 3. 如果任务队列满了,尝试添加一个新的非核心工作线程来执行任务
    else if (!addWorker(command, false))
        // 如果无法添加新的工作线程(可能因为线程池已经停止或者达到最大线程数限制),则拒绝任务
        reject(command);
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows vscode ssh 连接远程服务器

1.在 PowerShell 中运行以下命令&#xff0c;查看 OpenSSH 客户端是否已安装 Get-WindowsCapability -Online | Where-Object Name -like OpenSSH.Client*如果有安装的话&#xff0c;如下图 2.如果没有安装&#xff0c;那么用下面的命令进行安装 Get-WindowsCapability -On…

MQ基础知识

MQ基础 1.认识MQ 同步调用 我们现在基于OpenFeign的调用都属于是同步调用&#xff0c;那么这种方式存在哪些问题呢&#xff1f; 支付业务执行流程是这样的&#xff1a; 支付服务需要先调用用户服务完成余额扣减然后支付服务自己要更新支付流水单的状态然后支付服务调用交易…

信息安全数学基础(10)素数定理

前言 信息安全数学基础中的素数定理&#xff08;Prime Number Theorem&#xff09;是数论中一个非常重要的定理&#xff0c;它给出了小于或等于某个正整数x的素数的近似数量。这个定理在密码学、信息安全等领域有着广泛的应用&#xff0c;尤其是在设计加密算法时&#xff0c;对…

C++ —— 关于string类

目录 1. auto和范围for 1.1 auto关键字 1.2 范围for 2. string的三种遍历方式 3. string类的常用接口说明 3.1 成员函数 3.2 Iterators:&#xff08;迭代器&#xff09; 3.2.1正向迭代器和反向迭代器 3.3 Capacity&#xff08;容量&#xff09; 3.4 Modifiers&#x…

大模型微调十诫:关于将微调模型部署到生产环境的十条建议

转自;NLP工程化 大模型微调十诫&#xff1a;关于将微调模型部署到生产环境的十条建议&#xff1a; &#xff08;1&#xff09;不要盲目微调模型&#xff0c;先尝试使用提示的方式满足需求。只有当提示无法达到质量、性能或成本目标时&#xff0c;才考虑微调。 &#xff08;2…

ubuntu20.04 GLIBC从2.35降级到2.31

ubuntu20.04默认的GLIBC版本是2.31&#xff0c;因为某些库的依赖问题&#xff0c;脑子一抽把GLIBC升级到2.35&#xff0c;GLIBC升级参考一下另外一位博主的文章Ubuntu20.04更新GLIBC到2.35版本_glibc-2.35-CSDN博客 但当我想把GLIBC回退到2.31版本&#xff0c;参考网上的办法&a…

浅谈基于负荷时空均衡和弹性响应的电动汽车快充电价定价策略

摘要&#xff1a;为了引导电动汽车有序充电&#xff0c;提出了一种考虑负荷时空均衡和弹性响应的电动汽车快充电价定价策略。引入交通流理论描述交通路网&#xff0c;建立电动汽车快充负荷时空分布模型&#xff1b;考虑配电网调度和电动汽车快充负荷的弹性需求&#xff0c;构建…

【Python】从基础到进阶(七):深入理解Python中的异常处理与调试技巧

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、异常处理概述1. 什么是异常&#xff1f;2. 异常的捕获与处理 三、常见的异常类型四、自定义异常五、调试与日志记录1. 使用assert进行调试2. 使用日志记录 六、案例&#xff1a;文件操作与异常处理1. 需求分析2…

【经验技巧】瞬态信号仿真中的码型选择问题

工程师在进行通道信号仿真时&#xff0c;经常会遇到信号码型选择的问题&#xff0c;通常的码型选择有两种&#xff1a;连续周期变化、随机变化&#xff0c;那么&#xff0c;不同的码型会对结果产生截然不同的影响&#xff0c;以设计中一路差分通道为例&#xff0c;搭载信号传输…

51.【C语言】字符函数和字符串函数(strcpy函数)

承接50.【C语言】字符函数和字符串函数(上) 点我跳转 5.strcpy函数 *简单使用 cplusplus的介绍 点我跳转 strcpy:string copy 翻译: 复制字符串 复制由source指向的C字符串到由destionation指向的数组中,包括\0(终止0字符)(在\0那里停止复制) 为了防止溢出,由destionation指…

深入内核分析BindException异常原因

一、前言 前段时间公司内的站点发布时经常遇到Tomcat使用的8080端口被占用&#xff0c;导致启动报错BindException的情况。笔者参与了该问题的排查和修复&#xff0c;本文将深入Tomcat、OpenJDK、Linux内核等源码为大家讲解问题的原因以及排查过程。 报错信息 Caused by: java…

收到了大厂中秋礼盒,哪家赢了?

大家好&#xff0c;我是鸭鸭&#xff01; 中秋节越来越近啦&#xff0c;大家都收到放假通知和中秋月饼了吗&#xff1f; 各大互联网品牌大厂的中秋创意礼盒也来啦&#xff01; 字节 今年字节的中秋礼盒&#xff0c;除了广州酒家的月饼之外&#xff0c;还发了一床2m*2.3m的四…

INDEMIND:扫地机器人,仍然不够“香”

不仅需要“新花样”&#xff0c;还要搞好“基本功”。 行业祛魅&#xff0c;重啃技术战 正如所有人都知道市场会发生变化&#xff0c;但扫地机器人的陡然降温还是给大多数人上了一课。尽管到了2023年&#xff0c;市场有所复苏&#xff0c;但零售量的增长也仅为4%。一时间&…

医药|基于springboot的医药管理系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 计算机网络发展到现在已经好几十年了&#xff0c;在理论上面已…

基于vue框架的宠物管理平台的设计与实现f3193(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,医院简介,养宠知识,宠物分类,医生,预约医生,医嘱记录,宠物用品,用品分类,购买记录,供应商,宠物信息 开题报告内容 基于Vue框架的宠物管理平台的设计与实现开题报告 一、引言 随着宠物经济的兴起&#xff0c;宠物管理成为了一个日…

Win11+Ubuntu20.04双系统安装教程(避坑版)

Win11Ubuntu20.04双系统安装教程&#xff08;避坑版&#xff09; 前言系统盘制作安装Rufus系统盘制作 Windows磁盘配置移动分区&#xff08;磁盘分区时出现不连续的未分配空间需要用到&#xff0c;如果是连续的未分配空间即无需操作&#xff09;安装分区助手移动分区 安装Ubunt…

Redis的IO模型

Redis IO模型 Redis IO模型 使用的是基于 Reactor 模式的 I/O 多路复用模型。这个模型通过单线程事件循环来处理所有的客户端请求和响应。 基本模式 1. Reactor 模式 Reactor 模式是一种用于处理并发 I/O 操作的设计模式。它包含以下几个组件&#xff1a; 多路复用器&…

构建高效入学审核系统:Spring Boot解决方案

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理大学生入学审核系统的相关信息成为必然。开…

redis常见的数据类型?

参考&#xff1a;一文读懂Redis五种数据类型及应用场景 - 知乎 (zhihu.com) String 类型 String 类型&#xff1a;Redis 最基本的数据类型&#xff0c;它是二进制安全的&#xff0c;意味着你可以用它来存储任何类型的数据&#xff0c;如图片、序列化对象等。使用场景&#xff…

OceanBase 运维管理工具 OCP 4.x 升级:聚焦高可用、易用性及可观测性

可视化的管控平台&#xff0c;对 OceanBase 这类的分布式数据库及大规模数据的运维管理来说&#xff0c;是提升运维效率与数据库管理水平的重要工具。OceanBase 运维管理工具 OCP 作为专为OceanBase数据库设计的企业级全生命周期管理平台&#xff0c;为用户提供了全面的数据库可…