并发编程4:Java 中的并发基础构建模块
目录
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 异常的方法时,你自己的方法也就变成了一个阻塞方法,并且必须要处理对中断的响应。对于库代码来说,有两种基本选择://处理中断的策略
- 传递 InterruptedException。避开这个异常通常是最明智的策略,只需把 InterruptedException 传递给方法的调用者。传递 IterruptedException 的方法包括,根本不捕获该异常,或者捕获该异常,然后在执行某种简单的清理工作后再次抛出这个异常。
- 恢复中断。有时候不能抛出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 的子类来解决,在子类中为每个结果指定个逾期时间,并定期扫描缓存中逾期的元素。同样,它也没有解决缓存清理的问题,即移除旧的计算结果以便为新的计算结果腾出空间,从而使缓存不会消耗过多的内存。//使用缓存,应该考虑缓存的失效问题(存储时间)
至此,全文结束。
相关文章:
并发编程4:Java 中的并发基础构建模块
目录 1、同步容器类 1.1 - 同步容器类的问题 1.2 - 迭代和容器加锁 2、并发容器类 2.1 - ConcurrentHashMap 类 2.2 - CopyOnWriteArrayList 类 3、阻塞队列和生产者-消费者模式 3.1 - 串行线程封闭 4、阻塞方法与中断方法 5、同步工具类 5.1 - 闭锁 -> CountDow…...
Vue-10.集成(.editorconfig、.eslintrc.js、.prettierrc)
介绍 同时使用 .editorconfig、.prettierrc 和 .eslintrc.js 是很常见的做法,因为它们可以在不同层面上帮助确保代码的格式一致性和质量。这种组合可以在开发过程中提供全面的代码维护和质量保证。然而,这也可能增加一些复杂性,需要谨慎配置…...
PHP-FPM进程排查
1、查看php-fpm的进程个数 ps -ef |grep "php-fpm"|grep "pool"|wc -l2、查看每个php-fpm占用的内存大小 ps -ylC php-fpm --sort:rss3.查看PHP-FPM在你的机器上的平均内存占用 ps --no-headers -o "rss,cmd" -C php-fpm | awk { sum$1 } END…...
PHP-MD5注入
0x00 前言 有些零散的知识未曾关注过,偶然捡起反而更加欢喜。 0x01 md5 注入绕过 md5函数有两个参数,第一个参数是要进行md5的值,第二个值默认为false,如果为true则返回16位原始二进制格式的字符串。意思就是会将md5后的结果当…...
对redis、redisson、springcache总结
<一> redis-缓存中间件 什么是redis redis是c语言开发的,一个高性能key-value键值对内存数据库,可以用来做数据库、缓存、消息中间件的一种非关系型数据库。 redis数据存储在哪里 内存和磁盘中,但是redis的读写都在内存中,…...
Java基础知识实际应用(学生信息管理系统、猜拳小游戏、打印日历)
一、Java学生信息管理系统 这个系统包含了添加、修改、删除、查询和显示所有学生信息等功能。您可以在此基础上进行修改和完善,以适应您的需求。 import java.util.Scanner;public class StudentManagementSystem {private static Scanner scanner new Scanner(S…...
Git:在本地电脑上如何使用git?
git 版本: 2.40.1.windows.1 文章目录 一. 使用git之前你必须要理解的几个概念1.1 理解工作区、版本库、暂存区的概念1.2 提交Git版本库的步骤【分两步执行】 二. Git本地库实战2.1 初始化版本库2.2 新建 & 提交 & 状态2.3 查看日志2.4 回退 & 穿梭 &am…...
卷和分区的关系
1、分区 存储空间管理和仓库管理类似,只不过仓库管理的是货物,存储空间管理的是文件。当仓库规模小时,可以不划分货物的存放区域,但当仓库规模很大,就必须根据货物的类型和存储需要,把仓库分为多个区域。例…...
Linux下在qtcreator中创建qt程序
目录 1、新建项目 2、单工程项目创建 3、多工程项目创建 4、添加子工程(基于多工程目录结构) 5、 .pro文件 1、新建项目 切换到“编辑”界面,点击菜单栏中的“文件”-“新建文件或项目” 2、单工程项目创建 只有一个工程的项目&#…...
快递再多也不怕!你的顺丰快递用上5G“神器”
互联网时代,剁手党疯狂“买买买”之后,快递件量再创新高。《2023年6月中国快递发展指数报告》显示,2023二季度单月快递业务量稳定在百亿件以上。其中,由于“618”电商促销活动与父亲节叠加,6月16日至20日单日揽收量均超…...
微信小程序:模板使用
目录 模板的优点: 一、静态模板创建 二、静态模板使用 1.*.wxml引入模板 2.模板使用 3.*.wxss引入模板的样式 三、动态模板创建 四、动态模板使用 1.*.wxml引入模板 2.模板使用 3.*.js定义动态数据 五、结果展示 总结 模板的优点: 有利于保持网…...
AUTOSAR NvM Block的三种类型
Native NVRAM block Native block是最基础的NvM Block,可以用来存储一个数据,可以配置长度、CRC等。 Redundant NVRAM block Redundant block就是在Native block的基础上再加一个冗余块,当Native block失效(读取失败或CRC校验失…...
Vue+ElementUI实现选择指定行导出Excel
这里记录一下,今天写项目时 的一个需求,就是通过复选框选中指定行然后导出表格中选中行的Excel表格 然后这里介绍一个工具箱(模板):vue-element-admin 将它拉取后,运行就可以看到如下界面: 这里面的很多功能都已经实现…...
SNMP简单介绍
SNMP SNMP是广泛应用于TCP/IP网络的网络管理标准协议,该协议能够支持网络管理系统,用以监测连接到网络上的设备是否有任何引起管理上关注的情况。SNMP采用轮询机制,提供最基本的功能集,适合小型、快速、低价格的环境使用…...
使用python对图像加噪声
加上雨点噪声 import cv2 import numpy as npdef get_noise(img, value10):#生成噪声图像>>> 输入: img图像value 大小控制雨滴的多少 >>> 返回图像大小的模糊噪声图像noise np.random.uniform(0, 256, img.shape[0:2])# 控制噪声水平ÿ…...
以 Java NIO 的角度理解 Netty
文章目录 前言Java NIO 工作原理Selector 的创建ServerSocketChannel 的创建ServerSocketChannel 注册 Selector对事件的处理总结 前言 上篇文章《Netty 入门指南》主要涵盖了 Netty 的入门知识,包括 Netty 的发展历程、核心功能与组件,并且通过实例演示…...
Maven自定义脚手架(多module模块)+自定义参数
脚手架 视频教程: Maven保姆级教程 脚手架是一个项目模板,包含常用的工程结构、代码。 1 自定义脚手架 脚手架创建的步骤如下,先创建一个工程,把常用的代码写好,进入工程根目录,进行如下操作: …...
爬虫逆向实战(七)--猿人学第十六题
一、数据接口分析 主页地址:猿人学第十六题 1、抓包 通过抓包可以发现数据接口是api/match/16 2、判断是否有加密参数 请求参数是否加密? 通过查看“载荷”模块可以看出m是加密参数 请求头是否加密? 无响应是否加密? 无cook…...
Qt 杂项(Qwt、样式等)
Qt隐藏窗口边框 this->setWindowFlags(Qt::FramelessWindowHint);Qt模态框 this->setWindowModality(Qt::ApplicationModal);QLable隐藏border 代码中设置 lable->setStyleSheet("border:0px");或者UI中直接设置样式:“border:0px” Qwt开源…...
Python程序设计——列表
一、引言 关键点:一个列表可以存储任意大小的数据集合。 程序一般都需要存储大量的数值。假设,举个例子,需要读取100个数字,计算出它们的平均值,然后找出多少个数字是高于这个平均值的。程序首先读取100个数字并计算它…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
