JUC多并发编程 原子类

news2025/1/14 4:16:41

基本类型原子类

  • AtomicInteger
  • AtomicBoolean
  • AtomicLong
方法说明
public final int get()获取当前的值
public final int getAndSet(int newValue)获得当前的值,并设置新的值
public final int getAndIncrement()获得当前的值,并自增
public final int getAndDecrement()获得当前的值,并自减
public final getAndAdd(int delta)获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update)如果输入的数值等于预期值,则以原子方式将设置为输入值(update)
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class MyNumber{
    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void addPlusPlus() {
        atomicInteger.getAndIncrement();
    }
}
public class AtomicIntegerDemo {
    public static final int SIZE = 50;

    public static void main(String[] args) throws InterruptedException {
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);
        for (int i = 1; i <= SIZE; i++){
            new Thread(()-> {
                try{
                    for (int j = 1; j <= 1000; j++) {
                        myNumber.addPlusPlus();
                    }
                }finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }

        // 等待上面 50 个线程 全部计算完成后,再去获得最终值
        // try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        // 优化
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + "result:" + myNumber.atomicInteger.get());
    }
}

数组类型原子类

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray
import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayDemo {
    public static void main(String[] args) {
        // AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
        // AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);

        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});

        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            System.out.println(atomicIntegerArray.get(i));
        }

        int tmpInt = 0;
        tmpInt = atomicIntegerArray.getAndSet(0, 1122);
        System.out.println(tmpInt +"\t" + atomicIntegerArray.get(0));

        tmpInt = atomicIntegerArray.getAndIncrement(0);
        System.out.println(tmpInt +"\t" + atomicIntegerArray.get(0));
    }
}

引用类型原子类

  • AtomicReference 
  • AtomicStampedReference(版本号 + 1)
  • AtomicMarkableReference(状态戳简化为 treu | false)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;

public class AtomicMarkableReferenceDemo {
    static AtomicMarkableReference markableReference = new AtomicMarkableReference(100, false);

    public static void main(String[] args) {
        new Thread(()-> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
            try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            markableReference.compareAndSet(100, 1000, marked, !marked);
        },"t1").start();

        new Thread(()-> {
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
            try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
            System.out.println(Thread.currentThread().getName() + "\t" + "t2 线程 CAS result:" + b);
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());
        },"t2").start();
    }
}

对象的属性修改原子类

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
  • AtomicReferenceFieldUpdater

使用目的:

  • 以一种线程安全的方式操作非线程安全对象内的某些字段

使用要求:

  • 更新的对象属性必须使用 public volatile 修饰符
  • 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater() 创建一个更新器, 并且需要设置想要更新的类和属性

AtomicIntegerFieldUpdater:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

class BankAccount{
    String bankName = "CCB";
    public volatile int money = 0;
    public synchronized void add() {
        money ++;
    }

    AtomicIntegerFieldUpdater<BankAccount> fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
    public void transMoney(BankAccount bankAccount) {
        fieldUpdater.getAndIncrement(bankAccount);
    }
}
public class AtomicIntegerFieldUpdaterDemo {
    final static Integer SIZE = 10;
    public static void main(String[] args) throws InterruptedException {
        BankAccount bankAccount = new BankAccount();
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);
        for (int i = 1; i <= SIZE; i++){
            new Thread(()-> {
                try{
                    for (int j = 1; j <= 1000; j++) {
                        bankAccount.transMoney(bankAccount);
                    }
                }finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + "result:" + bankAccount.money);
    }
}

AtomicReferenceFieldUpdater:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

class MyVar{
    public volatile Boolean isInit = Boolean.FALSE;

    AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");

    public void init(MyVar myVar) {
        if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
            System.out.println(Thread.currentThread().getName() + "\t" + "----- start init, need 2 seconds");

            try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

            System.out.println(Thread.currentThread().getName() + "\t" + "----- over init");
        } else {
            System.out.println(Thread.currentThread().getName() + "\t" + "----- 已经有线程在进行初始化工作....");
        }
    }
}
public class AtomicReferenceFieldUpdaterDemo {
    public static void main(String[] args) {
        MyVar myVar = new MyVar();
        for (int i = 1; i <= 5; i++) {
            new Thread(()->{
                myVar.init(myVar);
            },String.valueOf(i)).start();
        }
    }
}

原子操作增强类原理深度解析

  • DoubleAccumulator:
  • DoubleAdder
  • LongAccumulator
  • LongAdder
方法名说明
void add(long x)将 当前的 value 加x
void increment()将当前的value 加 1
void decrement()将当前的 value 减 1
long sum()返回当前值,注意,在没有并发更新 value的情况下,sum 会返回一个精确值,在存在并发的情况下, sum 不保证返回精确值
void reset()将 value 重置为0, 用用于替代重新 new 一个 LongAdder, 但是此方法只可以在没有并发更新的情况下使用
long sumThenReset()获取当前 value, 并将 value 重置为 0

简单API 使用:

import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.LongBinaryOperator;

public class LongAdderAPIDemo {
    public static void main(String[] args) {
        // LongAdder 只能用来计算加法, 且从零开始计算
        LongAdder longAdder = new LongAdder();

        longAdder.increment();
        longAdder.increment();
        longAdder.increment();

        System.out.println(longAdder.sum());
        // 提供了自定义的函数操作
        //LongAccumulator longAccumulator = new LongAccumulator((x,y)-> x + y, 0);
        LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left + right;
            }
        },0);
        longAccumulator.accumulate(1);
        longAccumulator.accumulate(3);
        System.out.println(longAccumulator.get());
    }
}

高并发下性能比较:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

class ClickNumber{
    int number = 0;
    public synchronized void clickBySynchronized() {
        number++;
    }
    AtomicLong atomicLong = new AtomicLong(0);
    public  void clickByAtomicLong() {
        atomicLong.getAndIncrement();
    }

    LongAdder longAdder = new LongAdder();
    public void clickByLongAdder() {
        longAdder.increment();
    }

    LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0);
    public void clickByLongAccumulator(){
        longAccumulator.accumulate(1);
    }
}
public class AccmulatorCompareDemo {
    public static final int _1W = 10000;
    public static final int threadNumber = 50;

    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();
        long startTime;
        long endTime;
        CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);

        startTime = System.currentTimeMillis();

        for (int i = 1; i <= threadNumber; i++) {
            new Thread(()->{
                try{
                    for (int j = 1; j <= 100* _1W; j++) {
                        clickNumber.clickBySynchronized();
                    }
                }finally {
                    countDownLatch1.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("---- costTime:" + (endTime - startTime) + " 毫秒:" + "\t clickBySynchronized:" + clickNumber.number);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(()->{
                try{
                    for (int j = 1; j <= 100* _1W; j++) {
                        clickNumber.clickByAtomicLong();
                    }
                }finally {
                    countDownLatch2.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("---- costTime:" + (endTime - startTime) + " 毫秒:" + "\t clickByAtomicLong:" + clickNumber.number);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(()->{
                try{
                    for (int j = 1; j <= 100* _1W; j++) {
                        clickNumber.clickByLongAdder();
                    }
                }finally {
                    countDownLatch3.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("---- costTime:" + (endTime - startTime) + " 毫秒:" + "\t clickByLongAdder:" + clickNumber.number);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(()->{
                try{
                    for (int j = 1; j <= 100* _1W; j++) {
                        clickNumber.clickByLongAccumulator();
                    }
                }finally {
                    countDownLatch4.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("---- costTime:" + (endTime - startTime) + " 毫秒:" + "\t clickByLongAccumulator:" + clickNumber.number);

    }
}

LongAdder

  • LongAdder 是 Striped64的子类

Striped64 一些变量或方法定义:

变量或方法说明
base类似于 AtomicLong 中全局的 value。在没有竞争情况下数据直接累加到base 上,或者 cells 扩容时,也需要将数据写入到 base 上
collide表示扩容意向, false 一定不会扩容, true 可能会扩容
cellsBusy初始化 cells 或者 扩容 cells 需要获取锁, 0:表示无锁装固态 1: 表示其他线程已经持有了锁
casCellsBusy()通过 CAS 操作修改 cellsBusy 的值, CAS 成功代表获取锁, 返回 true
NCPU当前计算机 CPU 数量, Cell 数组扩容时使用到
getProbe()获取当前线程的 hash 值
advanceProbe()重置当前线程的 hash值

LongAdder 基本思想:

  • 基本思想就是分散热点,将 value 值分散到一个 Cell 数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行 CAS 操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的 long 值,只要将各个槽中的变量值累加返回。
  • sum() 会将所有 Cell 数组中的 value 和 base 累加作为返回值,核心思想就是将之前的 AtomicLong 一个 value 的更新压力分散到多个 value 中去。

LongAdder 源码分析:

LongAdder 在无竞争的情况,跟 AtomicLong 一样,对同一个 Base 进行操作,当出现竞争关系时是采用化整为零分散热点的做法,用空间换时间, 用一个数组 cells, 将一个 value 拆分进这个数组 cells。 多个线程需要同时对 value 进行操作时候,可以对线程 ID 进行 hash 得到 hash 值,再根据 hash 值映射到这个数组 cells 的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组 cells 的所有值 和 base 都加起来作为最终结果。

add(long x):

  • 最初无竞争时只更新 base
  • 如果更新 base 失败后,首次新建一个 Cell[] 数组
  • 当多个线程竞争同一个 Cell 比较激烈时, 可能就要对 Cell[] 扩容
    public void add(long x) {
        // as 表示 cells 引用, b 表示获取的 base 值, v 表示 期望值, m 表示 cells 数组的长度 a 表示 当前线程命中的 cell 单元格
        Cell[] as; long b, v; int m; Cell a;
        // 首次线程一定是false, 此时走 casBase 方法,以 CAS的方法更新 base 值,且只有当 CAS 失败时,才会走到 if 中
        // 条件1 cells 不为空
        // 条件2 cas 操作 base 失败, 说明其它线程先一步修改了 base 正在出现竞争
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            // true 无竞争, false 表示竞争激烈, 多个线程 hash到同一个 Cell, 可能要扩充
            boolean uncontended = true;
            // 条件1: cells 为空
            // 条件2: 应该不会出现
            // 条件3: 当前线程所在的 Cell 为空,说明当前线程还没有更新过 Cell,应该初始化 Cell
            // 条件4: 当更新当前线程所在的 Cell 失败,说明现在竞争很激烈,多个线程 hash 到同一个 Cell,应扩容
            if (as == null || (m = as.length - 1) < 0 ||
                // getProbe 返回当线程中的 threadLocalRandomProbe 字段
                // 它是通过随机数生成一个值, 对于一个确定的线程这个值是固定的(除非刻意修改它)
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                // 调用stringped64 中的方法处理
                longAccumulate(x, null, uncontended);
        }
    }

longAccumulate:

    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            if ((as = cells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) { // 当前线程的 hash 值 运算后映射得到的 Cell 单元为 Null, 说明该 Cell 没有被使用
                    if (cellsBusy == 0) {       // Cell[] 数组没有正在扩容
                        Cell r = new Cell(x);   // 创建了一个 Cell 单元
                        if (cellsBusy == 0 && casCellsBusy()) {    // 尝试加锁, 成功后cellsBusy == 1
                            boolean created = false;
                            try {               // 在有锁的情况下再检测一遍之前的判断
                                Cell[] rs; int m, j;    // 将Cell 单元附到 Cell[] 数组上
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       
                    wasUncontended = true;      // 只是重新设置了这个值为 true,紧接着执行 advanceProbe(h) 重置当前线程的 hash, 重新循环
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x)))) // 说明当前线程对应的数组中有了数据,也重置过 hash 值, 这时通过 cas 操作尝试对当前数组中的 value 值进行累加 x 的操作, x 默认为1, 如果 CAS 成功则直接跳出循环
                    break;
                else if (n >= NCPU || cells != as) 
                    collide = false;            // 如果 n 大于 CPU 最大数量,不可扩容, 并通过下面的 h = advanceProbe(h) 方法修改线程的 probe 再重新尝试
                else if (!collide) 
                    collide = true; // 如果扩容意向的 colide 是 false 则修改它为 true, 然后重新计算当前线程的 hash 值继续循环
                else if (cellsBusy == 0 && casCellsBusy()) {
                    // 当前的cells 数组和最新赋值的 as 是同一个, 代表没有被其他线程扩容过
                    try {
                        if (cells == as) {      // Expand table unless stale
                            // * 2 
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i]; // 拷贝
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        // 扩充是 2^n
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

sum():

  • 将所有 Cell 数组中的 value 和 base 累加作为返回值
  • 将之前的 AtomicLong 一个 value 的更新压力分散到多个 value 中去, 从而降级更新热点
  • sum 执行时, 并没有限制 base 和 cells 的更新, 所以 LongAdder 不是强一致性, 是最终一致性
    public long sum() {
        Cell[] as = cells; Cell a;
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

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

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

相关文章

C++内存管理之拷贝memcpy、分配malloc 与释放free

1.内存拷贝 memcpy C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1。 #声明&#xff1a; void *memcpy(void *str1, const void *str2, size_t n)#********************** str1 -- 指向用于存储复制内容的目标数组…

【数据结构】二叉树-OJ

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 一、相同的树 二、翻转二叉树 三、单值二叉树 四、 二叉树的最大深度 一、相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。如果两个…

第六章 3D地形搭建(上)

Unity 提供了多种工具来创建环境特征&#xff0c;例如地形和植被。要在场景中添加地形 (Terrain) 游戏对象&#xff0c;请从菜单中选择 GameObject > 3D Object > Terrain。此过程也会在 Project 视图中添加相应的地形资源。默认情况&#xff0c;场景中出现一个大型平坦的…

Spring AOP 代码加案例详解

Spring AOP目录 Spring AOP主要内容代理模式静态代理动态代理JDK动态代理CGLIB 动态代理JDK代理与CGLIB代理的区别 Spring AOP当前项目存在问题和解决Spring AOP的介绍AOP基本概念实现AOP的两种方式 Spring AOP —— Schema-based方式前置通知 - 入门案例思考后置通知异常通知环…

高压功率放大器在脉冲X射线源技术及火星X射线通信中的应用

实验名称&#xff1a;高速调制脉冲X射线源技术及火星X射线通信应用研究 研究方向&#xff1a;通信技术 测试目的&#xff1a; 火星是深空探测的热点区域&#xff0c;随着对火星探测的深入&#xff0c;未来火星探测器将面临传统通信方式难以应对的恶劣情况&#xff0c;例如火…

ROS学习5:ROS常用组件

【Autolabor初级教程】ROS机器人入门 1. TF 坐标变换 背景 现有一移动式机器人底盘&#xff0c;在底盘上安装了一雷达&#xff0c;雷达相对于底盘的偏移量已知&#xff0c;现雷达检测到一障碍物信息&#xff0c;获取到坐标分别为(x,y,z)&#xff0c;该坐标是以雷达为参考系的…

测试岗35k*16薪,性能测试面试题问什么?测试老鸟总结分析(附答案)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 软件测试面试题&am…

【Python/机器学习】不使用Conda安装Pytorch和Torchvision(Windows系统)

这篇文章介绍如何不使用conda等包管理系统在Windows系统上直接使用pip安装Pytorch和Torchvision。首先你需要有Python 3.6以上的64位环境&#xff08;32位是不可以的哟&#xff01;&#xff09;&#xff0c;并且假设你有NVIDIA显卡且已安装CUDA。 文章目录 1. 查看CUDA版本2. 找…

简记二分算法模板与代码案例:整数二分

本文以 Java 语言实现&#xff0c;整理的代码模板适用于编程竞赛。对代码模板原理的讲解不多&#xff0c;主要记录一下如何使用。 目录 一、算法模板&#xff1a;整数二分 二、例题 一、算法模板&#xff1a;整数二分 整数二分有两套算法模板&#xff0c;这两套算法模板几乎…

03 【Sass语法介绍-嵌套】

1.前言 在企业的实际项目开发中&#xff0c;Sass 的嵌套可以说是非常非常有用的&#xff0c;它可以让你的 CSS 代码易于管理和维护&#xff0c;看起来也比较清晰&#xff0c;这也是 Sass 中很基础的一个知识点&#xff0c;首先掌握这个至关重要&#xff01;在此章节我们将学习…

Golang题目总结

1. slice底层数据结构和扩容原理 数据结构 Go 的 slice 底层数据结构是由一个 array 指针指向底层数组&#xff0c;len 表示切片长度&#xff0c;cap 表示切片容量。扩容原理 &#xff08;1&#xff09;扩容思路&#xff1a;对于 append 向 slice 添加元素时&#xff0c;若 sl…

STM32-HAL-SPI-读写W25Q128FV-JEDEC ID(1)

文章目录 一、SPI串行通信协议1.1 SPI通信协议简介1.2 SPI工作原理1.3 SPI特性 二、W25Q128FV芯片介绍2.1 芯片基本参数介绍2.2 芯片管脚介绍2.3 技术手册等更多信息 三、开发板的板载Flash的连接电路四、测试准备五、初始化片上外设SPI15.1 初始化SPI15.2 设置片选引脚PB145.3…

【网页小功能 最简单入门!!!表白墙】【html+javaScript+css实现 简易 网页版 表白墙】

网页小功能 最简单入门&#xff01;&#xff01;&#xff01; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"…

详解达梦数据库字符串大小写敏感

检查数据库实例大小写敏感信息 场景一、初始化数据库实例为大小写敏感库 DDL操作 总结&#xff1a; 大小写敏感的数据库中&#xff1a; 创建表时&#xff1a; ①如果不对表名或列名添加""&#xff0c;那么表名和列名都自动转换为大写形式&#xff1b; ②如果对表…

自动化运维工具之Ansible

目录 一、自动化运维 1、通过xshell自动化运维 2、Ansible简介 3、Ansible特点及优势 4、Ansible核心程序 5、Ansible工作原理及流程 6、部署Ansible自动化运维工具 7、Ansible常用模块 (1) ansible命令行模块 (2) command模块 (3) shell模块 (4) cron模块 (5) us…

程序计算任意连续的12个月公里数不超三万公里预警

为了比亚迪的电池终身质保&#xff0c;写了个简单算法&#xff0c;计算任意12个连续的月份公里数加起来不超过3万公里的预警import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Scanner; import java.util.stream.Collectors;/***…

简单看看就会的tomcat全家桶(部署-多实例-监控-远程上传-日志-优化等)

tomcat学习 一&#xff0c;部署Tomcat 1.配置JDK环境 yum -y install java-1.8.0-openjdk-src.x86_64 #yum源安装JDK1.8 &#xff08;无须配置环境变量&#xff09;2.部署tomcat 下载地址&#xff1a;https://dlcdn.apache.org/tomcat/tomcat-8/v8.5.87/bin/apache-tomcat-…

基于STM32+华为云设计的智慧烟感系统

一、概述 当前基于STM32和华为云,设计了一种智慧烟感系统,该系统可以检测烟雾,同时将检测到的数据上传到云端进行处理和分析。系统可用于家庭、办公室等需要安装烟雾报警器场所。 二、系统设计 2.1 系统硬件设计 【1】硬件平台 该系统主要使用STM32F103ZET6微控制器作为…

点成案例丨点成生物为苏州某药企完成水浴IQOQ验证

点成科普 在生物制药、食品卫生相关实验室中&#xff0c;实验室仪器对产品质量具有重要影响&#xff0c;而实验室仪器在投入使用前的3Q验证&#xff08;IQ、OQ、PQ&#xff09;则是通过设备验证进行产品质量保证的重要部分。3Q验证的具体含义如下&#xff1a; 安装验证 Inst…

Windows系统被faust勒索病毒攻击勒索病毒解密服务器与数据库解密恢复

在近期&#xff0c;一种名为faust后缀的勒索病毒威胁已经引起了全球计算机系统安全领域的关注。faust勒索病毒是一种基于RSA加密算法的恶意软件&#xff0c;能够加密目标计算机系统上的所有文件&#xff0c;并向用户勒索赎金来承诺解密恢复操作。下面为大家介绍一下Windows系统…