并发编程4:Java 中的并发基础构建模块

news2024/11/14 18:43:58

目录

1、同步容器类

1.1 - 同步容器类的问题

1.2 - 迭代和容器加锁

2、并发容器类

2.1 - ConcurrentHashMap 类

2.2 - CopyOnWriteArrayList 类

3、阻塞队列和生产者-消费者模式

3.1 - 串行线程封闭

4、阻塞方法与中断方法

5、同步工具类

5.1 - 闭锁 -> CountDownLatch

5.2 - 使用 FutureTask 做闭锁

5.3 - 信号量 -> Semaphore

5.4 - 栅栏 -> Barrier

5.6 - 构建高效且可伸缩的结果缓存


        委托是创建线程安全类的一个最有效的策略:只需让现有的线程安全类管理所有的状态即可。Java 平台类库包含了丰富的并发基础构建模块,例如线程安全的容器类以及各种用于协调多个相互协作的线程控制流的同步工具类(Synchronizer)等。

        Java 并发类的 API 文档,点击这里。

1、同步容器类

        同步容器类包括 Vector Hashtable,二者是早期 JDK 的一部分,这些同步的封装器类是由 Collections.synchronizedXxx 等工厂方法创建的。这些类实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态

1.1 - 同步容器类的问题

        同步容器类都是线程安全的,但在某些情况下的复合操作也需要额外的客户端加锁来保护。容器上常见的复合操作包括:迭代(反复访问元素,直到遍历完容器中所有元素)、跳转(根据指定顺序找到当前元素的下一个元素)以及条件运算,例如 “若没有则添加” (检查在 Map 中是否存在键值 K,如果没有,就加入二元组(K,V))。//目前的很多并发容器也没有完全解决复合操作的问题,此外同步容器的加锁粒度比较大

1.2 - 迭代和容器加锁

        在迭代期间对容器进行加锁。如果容器的规模很大,或者在每个元素上执行操作的时间很长,那么这些线程将长时间等待。即使不存在饥饿或者死锁等风险,长时间地对容器加锁也会降低程序的可伸缩性。持有锁的时间越长,那么在锁上的竞争就可能越激烈,如果许多线程都在等待锁被释放,那么将极大地降低吞吐量和 CPU 的利用率//对整个容器加锁的弊端

        如果不希望在迭代期间对容器加锁,那么一种替代方法就是“克隆”容器,并在副本上进行迭代。由于副本被封闭在线程内,因此其他线程不会在选代期间对其进行修改,这样就避免抛出 ConcurrentModificationException (在克隆过程中仍然需要对容器加锁)。在克隆容器时存在显著的性能开销。这种方式的好坏取决于多个因素,包括容器的大小,在每个元素上执行的工作,迭代操作相对于容器其他操作的调用频率,以及在响应时间和吞吐量等方面的需求。//创建副本/快照对容器进行迭代是一种不错的思路

2、并发容器类

        Java 5.0 提供了多种并发容器类来改进同步容器的性能。同步容器将所有对容器状态的访向都串行化,以实现它们的线程安全性。这种方法的代价是严重降低并发性,当多个线程竞争
容器的锁时,吞叶量将严重减低。//同步容器类性能低

        另一方面,并发容器是针对多个线程并发访问设计的。在 Java 5.0 中增加了 ConcurrentHashMap,用来替代同步且基于散列的 Map,以及 CopyOnWriteArrayList,用于在遍历操作为主要操作的情况下代替同步的 List。在新的 ConcurrentMap 接口中增加了对一些常见复合操作的支持,例如“若没有则添加”、替换以及有条件删除等。//学习并发容器类重要的是操作和使用

通过并发容器来代替同步容器,可以极大地提高伸缩性并降低风险。

2.1 - ConcurrentHashMap 类

        与 HashMap 一样,ConcurrentHashMap 也是一个基于散列的 Map,但它使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。ConcurrentHashMap 并不是将每个方法都在同一个锁上同步并使得每次只能有一个线程访向容器,而是使用一种粒度更细的加锁机制来实现更大程度的共享,这种机制称为分段锁 (Lock Striping)。在这种机制中,任意数量的读取线程可以并发地访问 Map,执行读取操作的线程和执行写入操作的线程可以并发地访问 Map,并且一定数量的写入线程可以并发地修改 Map。ConcurrentHashMap 带来的结果是,在并发访问环境下将实现更高的吞吐量,而在单线程环境中只损失非常小的性能。//与HashTable相比具有更好的并发性能

        ConcurrentHashMap 与其他并发容器一起增强了同步容器类:它们提供的选代器不会抛出ConcurrentModificationException,因此不需要在选代过程中对容器加锁。ConcurrentHashMap 返回的选代器具有弱一致性 (Weakly Consistent),而并非“及时失败”。弱一致性的选代器可以容忍并发的修改,当创建迭代器时会遍历已有的元素,并可以(但是不保证)在选代器被构
造后将修改操作反映给容器。//只能反映暂时状态

2.2 - CopyOnWriteArrayList 类

        CopyOnWriteArrayList 用于替代同步 List,在某些情况下它提供了更好的并发性能,并且在迭代期间不需要对容器进行加锁或复制。(类似地,CopyOnWriteArraySet 的作用是替代同步 Set)

        “写入时复制(Copy-0n-Write)”容器的线程安全性在于,只要正确地发布一个事实不可变的对象,那么在访问该对象时就不再需要进一步的同步。在每次修改时,都会创建并重新发布一个新的容器副本,从而实现可变性。“写入时复制”容器的迭代器保留一个指向底层基础数组的引用,这个数组当前位于迭代器的起始位置,由于它不会被修改,因此在对其进行同步时只需确保数组内容的可见性。因此,多个线程可以同时对这个容器进行迭代,而不会彼此干扰或者与修改容器的线程相互干扰。“写人时复制”容器返回的迭代器不会抛出ConcurrentModificationException,并且返回的元素与迭代器创建时的元素完全一致,而不必考虑之后修改操作所带来的影响。

3、阻塞队列和生产者-消费者模式

        阻塞队列支持生产者 - 消费者这种设计模式。该模式将“找出需要完成的工作”与“执行工作”这两个过程分离开来,并把工作项放人一个“待完成”列表中以便在随后处理,而不是找出后立即处理。生产者 - 消费者模式能简化开发过程,因为它消除了生产者类和消费者类之间的代码依赖性,此外,该模式还将生产数据的过程与使用数据的过程解耦开来以简化工作负载的管理,因为这两个过程在处理数据的速率上有所不同。//BlockingQueue

在构建高可靠的应用程序时,有界队列是一种强大的资源管理工具:它们能抑制并防止产生过多的工作项,使应用程序在负荷过载的情况下变得更加健壮。

        Java 中的阻塞队列: 

3.1 - 串行线程封闭

        在 java.utilconcurrent 中实现的各种阻塞队列都包含了足够的内部同步机制,从而安全地将对象从生产者线程发布到消费者线程。

        对于可变对象,生产者 -消费者这种设计与阻塞队列一起,促进了串行线程封闭,从而将对象所有权从生产者交付给消费者。线程封闭对象只能由单个线程拥有,但可以通过安全地发布该对象来“转移”所有权。在转移所有权后,也只有另一个线程能获得这个对象的访问权限,并且发布对象的线程不会再访问它。这种安全的发布确保了对象状态对于新的所有者来说是可见的,并且由于最初的所有者不会再访问它,因此对象将被封闭在新的线程中。新的所有者线程可以对该对象做任意修改,因为它具有独占的访问权。

        对象池利用了串行线程封闭,将对象“借给”一个请求线程。只要对象池包含足够的内部同步来安全地发布池中的对象,并且只要客户代码本身不会发布池中的对象,或者在将对象返回给对象池后就不再使用它,那么就可以安全地在线程之间传递所有权。//线程封闭就是在封闭对象上始终只有一个线程进行操作(串行化)

4、阻塞方法与中断方法

        Thread 提供了 interrupt 方法,用于中断线程或者查询线程是否已经被中断。每个线程都有一个布尔类型的属性,表示线程的中断状态,当中断线程时将设置这个状态。

        中断是一种协作机制。一个线程不能强制其他线程停止正在执行的操作而去执行其他的操作。当线程 A 中断 B 时,A 仅仅是要求 B 在执行到某个可以暂停的地方停止正在执行的操作一一前提是如果线程 B 愿意停止下来。虽然在 API 或者语言规范中并没有为中断定义任何特定应用级别的语义,但最常使用中断的情况就是取消某个操作。方法对中断请求的响应度越高,就越容易及时取消那些执行时间很长的操作。

        当在代码中调用了一个将抛出 InterruptedException 异常的方法时,你自己的方法也就变成了一个阻塞方法,并且必须要处理对中断的响应。对于库代码来说,有两种基本选择://处理中断的策略

  1. 传递 InterruptedException。避开这个异常通常是最明智的策略,只需把 InterruptedException 传递给方法的调用者。传递 IterruptedException 的方法包括,根本不捕获该异常,或者捕获该异常,然后在执行某种简单的清理工作后再次抛出这个异常。
  2. 恢复中断。有时候不能抛出InterruptedException,例如当代码是 Runnable 的一部分时,在这些情况下,必须捕获 InterruptedException,并通过调用当前线程上的 interrupt 方法恢复中断状态,这样在调用栈中更高层的代码将看到引发了一个中断。

5、同步工具类

        所有的同步工具类都包含一些特定的结构化属性:它们封装了一些状态,这些状态将决定执行同步工具类的线程是继续执行还是等待,此外还提供了一些方法对状态进行操作,以及另一些方法用于高效地等待同步工具类进入到预期状态。

        Java 中提供的一些同步工具类:

5.1 - 闭锁 -> CountDownLatch

        闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能通过当到达结束状态时,这扇门会打开并允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。闭锁可以用来确保某些活动直到其他活动都完成后才继续执行。//闭锁一旦打开就不再关闭,-> CountDownLatch

        CountDownLatch 是一种灵活的闭锁实现,可以在上述各种情况中使用,它可以使一个或多个线程等待一组事件发生。闭锁状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。countDown 方法递减计数器,表示有一个事件已经发生了,而 await 方法等待计数器达到零,这表示所有需要等待的事件都已经发生。如果计数器的值非零,那么 await 会一直阻塞直到计数器为零,或者等待中的线程中断,或者等待超时。//闭锁的实现原理,内置事件计数器

5.2 - 使用 FutureTask 做闭锁

        FutureTask 也可以用做闭锁。(FutureTask 实现了 Future 语义,表示一种抽象的可生成结果的计算)。FutureTask 表示的计算是通过 Callable 来实现的,相当于一种可生成结果的 Runnable,并且可以处于以下 3 种状态:等待运行 (Waiting to run),正在运行(Running)和运行完成(Completed)。“执行完成”表示计算的所有可能结束方式,包括正常结束、由于取消而结束和由于异常而结束等。当FutureTask 进入完成状态后,它会永远停止在这个状态上。//FutureTask表示可生成结果的计算

        Future.get 的行为取决于任务的状态。如果任务已经完成,那么 get 会立即返回结果,否则get 将阻塞直到任务进入完成状态,然后返回结果或者抛出异常。FutureTask 将计算结果从执行计算的线程传递到获取这个结果的线程,而 FutureTask 的规范确保了这种传递过程能实现结果的安全发布。//将计算和获取结果进行分离,获取结果的过程优点类似于从阻塞队列中获取数据

        下列代码中,Preloader 就使用了 FutureTask 来执行一个高开销的计算,并且计算结果将在稍后使用。通过提前启动计算,可以减少在等待结果时需要的时间。

public class Preloader {

    ProductInfo loadProductInfo() throws DataLoadException {
        return null;
    }

    private final FutureTask<ProductInfo> future = new FutureTask<>(() -> loadProductInfo());

    private final Thread  thread = new Thread(future);

    public void start() {
        //执行任务
        thread.start();
    }

    public ProductInfo get() throws DataLoadException, InterruptedException {
        try {
            //获取结果
            return future.get();
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof DataLoadException) {
                throw (DataLoadException) cause;
            } else {
                throw LaunderThrowable.launderThrowable(cause);
            }
        }
    }

    interface ProductInfo {
    }
}

class DataLoadException extends Exception {

}

5.3 - 信号量 -> Semaphore

        计数信号量(Counting Semaphore) 用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。计数信号量还可以用来实现某种资源池,或者对容器施加边界。

        Semaphore 中管理着一组虚拟的许可 (permit),许可的初始数量可通过构造函数来指定。在执行操作时可以首先获得许可(只要还有剩余的许可),并在使用以后释放许可。如果没有许可,那么 acquire 将阻塞直到有许可(或者直到被中断或者操作超时)。release 方法将返回一个许可给信号量。计算信号量的一种简化形式是二值信号量,即初始值为 1 的 Semaphore。二值信号量可以用做互斥体(mutex),并具备不可重人的加锁语义:谁拥有这个唯一的许可,谁就拥有了互斥锁//信号量不可重入,所以信号量不能用来替换可重入锁

5.4 - 栅栏 -> Barrier

        我们已经看到通过闭锁来启动一组相关的操作,或者等待一组相关的操作结束。闭锁是一次性对象,一旦进入终止状态,就不能被重置。

        栅栏(Barrier)类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。//栅栏等待线程,闭锁等待事件(事件具有一次性)

        CyclicBarrier 可以使一定数量的参与方反复地在栅栏位置汇集,它在并行选代算法中非常有用:这种算法通常将一个问题拆分成一系列相互独立的子问题。当线程到达栅栏位置时将调用 await 方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达了栅栏位置,那么栅栏将打开,此时所有线程都被释放,而栅栏将被重置以便下次使用//栅栏可重用

        另一种形式的栅栏是 Exchanger,它是一种两方 (Two-Party)栅栏,各方在栅栏位置上交换数据。当两方执行不对称的操作时,Exchanger 会非常有用,例如当一个线程向缓冲区写入数据,而另一个线程从缓冲区中读取数据。这些线程可以使用 Exchanger 来汇合,并将满的缓冲区与空的缓冲区交换。当两个线程通过 Exchanger 交换对象时,这种交换就把这两个对象安全地发布给另一方。//用来处理数据交换

5.6 - 构建高效且可伸缩的结果缓存

        几乎所有的服务器应用程序都会使用某种形式的缓存。重用之前的计算结果能降低延迟提高吞吐量,但却需要消耗更多的内存//用空间换时间

        像许多“重复发明的轮子”一样,缓存看上去都非常简单。然而,简单的缓存可能会将性能瓶颈转变成可伸缩性瓶颈,即使缓存是用于提升单线程的性能。

        下边是一个高效且可伸缩的缓存的开发示例代码,用于改进一个高计算开销的函数。该示例具有很强的参考价值:

public class Memoizer <A, V> implements Computable<A, V> {
    //使用ConcurrentHashMap缓存计算结果,提升性能
    //使用ConcurrentMap<A, Future<V>>而不是ConcurrentMap<A, V>保存计算结果,避免重复计算
    private final ConcurrentMap<A, Future<V>> cache = new ConcurrentHashMap<>();
    private final Computable<A, V> c;

    public Memoizer(Computable<A, V> c) {
        this.c = c;
    }

    public V compute(final A arg) throws InterruptedException {
        while (true) {
            //1-计算时先获取,如果已经有线程计算,那么就阻塞等待获取前一个线程的计算结果
            Future<V> f = cache.get(arg);
            if (f == null) {
                //2-封装计算任务,若没有则添加
                Callable<V> eval = () -> c.compute(arg);
                FutureTask<V> ft = new FutureTask<V>(eval);
                f = cache.putIfAbsent(arg, ft); //原子性->若没有则添加
                if (f == null) {
                    f = ft;
                    ft.run(); //3-执行任务
                }
            }
            try {
                //4-获取计算结果:可能会抛出异常,需对异常进行处理
                return f.get();
            } catch (CancellationException e) {
                //5-如果执行被取消,需要移除计算任务,给其他线程计算机会
                cache.remove(arg, f);
            } catch (ExecutionException e) {
                //6-执行异常直接抛出
                throw LaunderThrowable.launderThrowable(e.getCause());
            }
        }
    }
}

interface Computable <A, V> {
    V compute(A arg) throws InterruptedException;
}

class ExpensiveFunction implements Computable<String, BigInteger> {
    public BigInteger compute(String arg) {
        return new BigInteger(arg);
    }
}

        注意:Memoizer 没有解决缓存逾期的问题,但它可以通过使用 FutureTask 的子类来解决,在子类中为每个结果指定个逾期时间,并定期扫描缓存中逾期的元素。同样,它也没有解决缓存清理的问题,即移除旧的计算结果以便为新的计算结果腾出空间,从而使缓存不会消耗过多的内存。//使用缓存,应该考虑缓存的失效问题(存储时间)

        至此,全文结束。

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

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

相关文章

【分布式共识算法】Basic Paxos 算法

basic paxos算法&#xff1a;描述的是多个节点就某个值达成共识。 muti-paxos 算法&#xff1a;描述的是执行多个basic paxos实例&#xff0c;就一系列值达成共识。 共识其实&#xff0c;比如当多个客户端请求服务器&#xff0c;修改同一个值X 多个阶段达成共识。 原理 角色…

213、仿真-基于51单片机智能电表电能表用电量电费报警Proteus仿真设计(程序+Proteus仿真+原理图+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、原理图 五、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选…

ARM DIY 硬件调试

前言 之前打样的几块 ARM 板&#xff0c;一直放着没去焊接。今天再次看到&#xff0c;决定把它焊起来。 加热台焊接 为了提高焊接效率&#xff0c;先使用加热台焊接。不过板子为双面贴片&#xff0c;使用加热台只能焊接一面&#xff0c;那就优先焊主芯片那面&#xff0c;并…

分布式链路追踪——Dapper, a Large-Scale Distributed Systems Tracing Infrastructure

要解决的问题 如何记录请求经过多个分布式服务的信息&#xff0c;以便分析问题所在&#xff1f;如何保证这些信息得到完整的追踪&#xff1f;如何尽可能不影响服务性能&#xff1f; 追踪 当用户请求到达前端A&#xff0c;将会发送rpc请求给中间层B、C&#xff1b;B可以立刻作…

End-to-End Object Detection with Transformers

DERT 目标检测 基于卷积神经网络的目标检测回顾DETR对比Swin Transformer摘要检测网络流程DERT网络架构编码器概述解码器概述整体结构object queries的初始化Decoder中的Muiti-Head Self-AttentionDecoder中的Muiti-Head Attention 损失函数解决的问题 基于卷积神经网络的目标检…

使用JavaMail发送邮件时嵌入公司logo图片

使用JavaMail发送邮件时嵌入公司logo图片 第一种方式&#xff1a;img标签和logo图片链接第二种方式&#xff1a;使用img标签和图片base64字符串第三种方式&#xff08;推荐&#xff09;&#xff1a;将logo当做附件一起发送并设置ContentID&#xff0c;再使用img标签&#xff0c…

C++——移动构造和完美转发

1.什么是右值 右值引用是C11的概念&#xff0c;与之对应的是左值引用。 当一个对象被用作右值的时候&#xff0c;用的是对象的值(内容)&#xff1b;当对象被用作左值的时候&#xff0c;用的是对象的身份(在内存当中的位置)。 以上的概念是摘录自《C primer》。 但是这样的概…

【SentenceTransformer系列】计算句子嵌入的概念(01/10)

一、说明 要分清词嵌入和句子嵌入的区别。 句子嵌入是指将句子或文档表示为固定长度的向量的过程&#xff0c;使得向量能够捕获句子的语义和上下文信息。它是自然语言处理 (NLP) 和机器学习中的常见任务&#xff0c;因为它可以帮助对句子之间的关系和相似性进行建模&#xff0c…

接口自动化测试(添加课程接口调试,调试合同上传接口,合同列表查询接口,批量执行)

1、我们把信息截取一下 1.1 添加一个新的请求 1.2 对整个请求进行保存&#xff0c;Ctrl S 2、这一次我们添加的是课程添加接口&#xff0c;以后一个接口完成&#xff0c;之后Ctrl S 就能够保存 2.1 选择方法 2.2 设置请求头&#xff0c;参数数据后期我们通过配置设置就行 3、…

Lua 位和字节

一、位运算 从 Lua 5.3 版本开始&#xff0c;提供了针对数值类型的一组标准位运算符&#xff0c;与算数运算符不同的是&#xff0c;运算符只能用于整型数。 运算符描述&按位与|按位或&#xff5e;按位异或>>逻辑右移<<逻辑左移&#xff5e;&#xff08;一元运…

安全学习DAY17_信息打点-语言框架组件识别

信息打点-WEB打点-语言框架&开发组件 文章目录 信息打点-WEB打点-语言框架&开发组件本节涉及链接&工具本节知识&思维导图基础概念介绍框架&#xff1a;组件&#xff1a;Web架构 对应Web测试手法后端&#xff1a;前端组件&#xff1a;java居多&#xff0c;框架&…

RP2040开发板自制树莓派逻辑分析仪

目录 前言 1 准备工作和前提条件 1.1 Raspberry Pi Pico RP2040板子一个 1.2 Firmware-LogicAnalyzer-5.0.0.0-PICO.uf2固件 1.3 LogicAnalyzer-5.0.0.0-win-x64软件 2 操作指南 2.1 按住Raspberry Pi Pico开发板的BOOTSEL按键&#xff0c;再接上USB接口到电脑 2.2 刷入…

产品帮助中心怎么做?这两点不能忽略,让用户自助解决问题!

对于大部分线上产品&#xff0c;因为其功能和系统的复杂性&#xff0c;使得新手客户入门学习非常复杂&#xff0c;为了快速响应并且解决问题&#xff0c;一套系统完整的产品帮助中心必不可少&#xff01; 产品帮助中心 因此&#xff0c;对于很多产品开发者来说&#xff0c;借助…

pg简单使用

1.创建服务器 2.创建数据库 3.修改默认连接数据库 工具都是链接到这里 4.数据库代码工具

ByteBuffer 使用

ByteBuffer 使用 1 java.nio包中的类定义的缓冲区类型2 缓冲区常用属性2.1缓冲区的容量(capacity)2.2 缓冲区的位置(position)2.3 缓冲区的限制(limit)2.4 缓冲区的标记(mark)2.5 剩余容量 remaining/hasRemaining 3 缓冲区常用方法3.1 创建缓冲区3.1.1 allocate方法3.1.2 wrap…

交叉编译之wiringPi库,【全志H616,orangepi-zero2】

文章目录 书接上回wiringPi全志库下载建立软链接软连接软连接创建 硬链接硬链接创建 测试树莓派运行servo文件 结束 书接上回 上回已经完整的安装了全志的gcc交叉编译工具 https://blog.csdn.net/qq_52749711/article/details/132306764 wiringPi全志库下载 下载链接 先搞到…

Jmeter+ant+jenkins实现持续集成

jmeterantjenkins持续集成 一、下载并配置jmeter 首先下载jmeter工具&#xff0c;并配置好环境变量&#xff1b;参考&#xff1a;https://www.cnblogs.com/YouJeffrey/p/16029894.html jmeter默认保存的是.jtl格式的文件&#xff0c;要设置一下bin/jmeter.properties,文件内容…

中国电信物联网收入33亿元,用户达到4.73亿户!

近日&#xff0c;中国电信发布2023中期业绩&#xff0c;物联网迎来强劲增长&#xff0c;物联网收入33亿元&#xff0c;同比增长75.7%&#xff0c;物联网用户4.73亿户&#xff0c;同比增长31.5%。天翼物联自主研发的AIoT物联网平台&#xff0c;升级为云原生3AZ架构&#xff0c;提…

在线课堂录播直播管理系统SpringBoot+Vue

在线课堂录播直播管理系统SpringBootVue 文章目录 在线课堂录播直播管理系统SpringBootVue共三个端&#xff1a;后端、后台管理系统、前端&#xff0c;如要学习看评论区&#xff08;全部源码、文档、数据库&#xff09;。内置功能一、前端二、后台管理三、后端--代码全有。四、…

k8s 认证和权限控制

k8s 的认证机制是啥&#xff1f; 说到 k8s 的认证机制&#xff0c;其实之前咋那么也有提到过 ServiceAccouont &#xff0c;以及相应的 token &#xff0c;证书 crt&#xff0c;和基于 HTTP 的认证等等 k8s 会使用如上几种方式来获取客户端身份信息&#xff0c;不限于上面几种…