java原子类-Atomic

什么是原子类?

java 1.5引进原子类,具体在java.util.concurrent.atomic包下,atomic包里面一共提供了13个类,分为4种类型,分别是:

  • 原子更新基本类型
  • 原子更新数组
  • 原子更新引用
  • 原子更新属性。

原子类也是java实现同步的一套解决方案。

什么时候使用原子类?

当我们只是需要一个简单的、高效、线程安全的递增或者递减方案:

  1. 简单:操作简单,底层实现j简单
  2. 高效:占用资源少,操作速度快
  3. 安全:在高并发和多线程环境下要保证数据的正确性

当然这种情况可以使用synchronized关键字和lock可以实现,但是代码量会上去,而且性能也会降低一点,所以用原子类就比较方便一点

原子变量类简单介绍

原子变量类在java.util.concurrent.atomic包下,总体来看有这么多个:

基本类型:

  • AtomicBoolean:布尔型
  • AtomicInteger:整型
  • AtomicLong:长整型

数组:

  • AtomicIntegerArray:数组里的整型
  • AtomicLongArray:数组里的长整型
  • AtomicReferenceArray:数组里的引用类型

引用类型:

  • AtomicReference:引用类型
  • AtomicStampedReference:带有版本号的引用类型
  • AtomicMarkableReference:带有标记位的引用类型

对象的属性:

  • AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值,基于反射的使用程序,可以对指定类的指定的volatile int字段进行原子更新
  • AtomicLongFieldUpdater:原子更新对象中Long类型字段的值,基于反射的使用程序,可以对指定类的指定的volatile long字段进行原子更新
  • AtomicReferenceFieldUpdater:原子更新引用类型字段的值,基于反射的使用程序,可以对指定类的指定的volatile volatile引用进行原子更新

JDK8新增

Accumulator累加器

  • DoubleAccumulator、
  • LongAccumulator、

Adder累加器

  • DoubleAdder
  • LongAdder

是对AtomicLong等类的改进。比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。

Atomic包里的类基本都是使用Unsafe实现的包装类。 从原理上来说就是:Atomic包的类的实现大多数都是调用的unsafe方法,而unsafe底层实际上是调用C代码,C代码调用汇编,最后生成出一条CPU指令cmpxchg,完成操作,这也是为什么CAS是原子性操作,因为是一条CPU指令,不会被打断。

原子变量类的使用

基本类型原子类(Atomic*)

这里以原子更新基本类型中的AtomicInteger类为例,介绍通用的API接口和使用方法。

常用的API:

public final int set() //设一个值
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果当前值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue) //最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
    AtomicInteger a = new AtomicInteger(0);
        for (int i = 1; i < 5; i++) {
            a.getAndIncrement(); // a 自增,相当于 a ++
        }
        //获取当前a的值
        System.out.println("AtomicInteger a从0自增4次结果为:"+ a.get());
​
        System.out.println("AtomicInteger 当前a为:"+ a.getAndDecrement() + ",并自减一次"); //a --
​
        //获取当前a的值,并更新a为8
        System.out.println("AtomicInteger a当前值为:"+a.getAndSet(8)+",并更新a为8");
​
        //获取当前a的值,并将a加6
        System.out.println("AtomicInteger a当前值为:"+a.getAndAdd(6)+",并将a加6");
​
        a.compareAndSet(12,9); //如果a=12,就把a更新为9,否则不进行操作
        System.out.println("AtomicInteger a当前值为:"+a.get());
​
        a.compareAndSet(14,9); //如果a=14,就把a更新为9,否则不进行操作
        System.out.println("AtomicInteger a当前值为:"+a.get());
​

AtomicInteger a从0自增4次结果为:4 AtomicInteger 当前a为:4,并自减一次 AtomicInteger a当前值为:3,并更新a为8 AtomicInteger a当前值为:8,并将a加6 AtomicInteger a当前值为:14 AtomicInteger a当前值为:9

数组类型原子类(Atomic*Array)

这里以AtomicIntegerArray 为例,介绍通用的API接口和使用方法。

常用API:

public final int get(int i) //获取 index=i 位置元素的值
public final int set(int i, int newValue) //为 index=i 位置元素设新值
public final int getAndSet(int i, int newValue) //返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果index=i 位置的值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue) //最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

基本使用:

         int[] a = {1,1,1,1};
        AtomicIntegerArray arr = new AtomicIntegerArray(a);
        System.out.println("arr数组初始值为:" +arr.toString());
​
        for (int i = 0; i < 4; i++) {
            arr.getAndIncrement(i); //index = i位置上的值arr[i]自增,相当于 a[i] ++
        }
        System.out.println("arr数组每个元素都自增1后为:" +arr.toString());
        //获取当前arr[1]的值
        System.out.println("arr[1]的值为:"+ arr.get(1));
​
        System.out.println("arr[2]当前值为:"+ arr.getAndDecrement(2) + ",并让arr[2]自减一次"); //a[2]--
​
        //获取当前a[2]的值,并更新a为8
        System.out.println("arr[2]当前值为:"+arr.getAndSet(2,8)+",并更新a[2]为8");
​
        //获取当前a的值,并将a加6
        System.out.println("arr[2]当前值为:"+arr.getAndAdd(2,6)+",并将a[2]加6");
​
        arr.compareAndSet(2,12,9); //如果a[2]=12,就把a[2]更新为9,否则不进行操作
        System.out.println("arr[2]当前值为:"+arr.get(2));
​
        arr.compareAndSet(2,14,9); //如果a[2]=14,就把a[2]更新为9,否则不进行操作
        System.out.println("arr[2]当前值为:"+arr.get(2));

arr数组初始值为:[1, 1, 1, 1] arr数组每个元素都自增1后为:[2, 2, 2, 2] arr[1]的值为:2 arr[2]当前值为:2,并让arr[2]自减一次 arr[2]当前值为:1,并更新a[2]为8 arr[2]当前值为:8,并将a[2]加6 arr[2]当前值为:14 arr[2]当前值为:9

引用类型原子类(Atomic*Reference)

User类:

package 原子类;
​
public class User {
        private String name;
        private int age;
​
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
​
        public String getName() {
            return this.name;
        }
​
        public void setName(final String name) {
            this.name = name;
        }
​
        public int getAge() {
            return this.age;
        }
​
        public void setAge(final int age) {
            this.age = age;
        }
​
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + ''' +
                    ", age=" + age +
                    '}';
    }
​
}

 package 原子类;


import java.util.concurrent.atomic.AtomicReference;
​
public class Demo3 {
​
    public static void main(String[] args) {
        AtomicReference<User> atu=new AtomicReference<>();
​
        User user1=new User("张三",10);
        User user2=new User("李四",16);
        User user3=new User("王五",19);
        atu.set(user1);
        //常用的API和前面的是差不多的
        System.out.println(atu.getAndSet(user2));
        System.out.println(atu.get());
​
        System.out.println(atu.compareAndSet(user2,user3));
        System.out.println(atu.get());
    }
//User{name='张三', age=10}
//User{name='李四', age=16}
//true
//User{name='王五', age=19}
​
​
}
什么是ABA问题?

ABA问题指在CAS操作过程中,当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。这时候就可能导致程序出现意外的结果。

在高并发场景下,使用CAS操作可能存在ABA问题,也就是在一个值被修改之前,先被其他线程修改为另外的值,然后再被修改回原值,此时CAS操作会认为这个值没有被修改过,导致数据不一致。

如何解决?

为了解决ABA问题,Java中提供了AtomicStampedReference类(原子标记参考),该类通过使用版本号的方式来解决ABA问题。每个共享变量都会关联一个版本号,CAS操作时需要同时检查值和版本号是否匹配。因此,如果共享变量的值被改变了,版本号也会发生变化,即使共享变量被改回原来的值,版本号也不同,因此CAS操作会失败。

AtomicStampedReference<V>:解决ABA问题

带版本号的引用类型原子类,可以解决ABA问题

AtomicStampedReference在构建的时候需要一个类似于版本号的int类型变量stamped,每一次针对共享数据的变化都会导致该 stamped 的变化(stamped 需要应用程序自身去负责,AtomicStampedReference并不提供,一般使用时间戳作为版本号),因此就可以避免ABA问题的出现,AtomicStampedReference的使用也是极其简单的,创建时我们不仅需要指定初始值,还需要设定stamped的初始值,在AtomicStampedReference内部会将这两个变量封装成Pair对象

package 原子类;
import 原子类.User;
import java.util.concurrent.atomic.AtomicStampedReference;
class ABADemo {
    public static void main(String[] args) {
        User zs = new User("张三",18);
        User ls = new User("李四",25);
        //创建对象带版本号的引用类型原子类,添加对象和初始化的版本号
        AtomicStampedReference<User> reference = new AtomicStampedReference<>(zs, 1);
​
        new Thread(() -> {
            int stamp = reference.getStamp();
            User referenceUser = reference.getReference();
            // 保证线程t2可以拿到版本号
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // A
            System.out.println(Thread.currentThread().getName() + "版本号:" + stamp + " 对象" + referenceUser);
            // B
            boolean compareAndSet = reference.compareAndSet(referenceUser, ls, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "将数据设置" + (compareAndSet?"成功 ":"失败 ") + reference.getReference());
            // A
            compareAndSet = reference.compareAndSet(reference.getReference(),zs,reference.getStamp(),reference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "将数据设置" + (compareAndSet?"成功 ":"失败 ") + reference.getReference());
​
        },"t1").start();
​
        new Thread(() -> {
            int stamp = reference.getStamp();
            User referenceUser = reference.getReference();
            // 保证线程t1发生完ABA问题
            try {
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean compareAndSet = reference.compareAndSet(referenceUser,ls,stamp,stamp + 1);
            System.out.println(Thread.currentThread().getName() + "将数据设置" + (compareAndSet?"成功 ":"失败 ") + reference.getReference());
​
        },"t2").start();
    }
}

t1版本号:1 对象User{name='张三', age=18} t1将数据设置成功 User{name='李四', age=25} t1将数据设置成功 User{name='张三', age=18} t2将数据设置失败 User{name='张三', age=18}

常用API:

// 构造函数,初始化引用和版本号
public AtomicStampedReference(V initialRef, int initialStamp)
 
// 以原子方式获取当前引用值
public V getReference()
 
// 以原子方式获取当前版本号
public int getStamp()
 
// 以原子方式获取当前引用值和版本号
public V get(int[] stampHolder)
 
// 以原子的方式同时更新引用值和版本号
// 当期望引用值不等于当前引用值时,操作失败,返回false
// 当期望版本号不等于当前版本号时,操作失败,返回false
// 在期望引用值和期望版本号同时等于当前值的前提下
// 当新的引用值和新的版本号同时等于当前值时,不更新,直接返回true
// 当新的引用值和新的版本号不同时等于当前值时,同时设置新的引用值和新的版本号,返回true
public boolean weakCompareAndSet(V  expectedReference,
                                 V  newReference,
                                 int expectedStamp,
                                 int newStamp)
 
// 以原子的方式同时更新引用值和版本号
// 当期望引用值不等于当前引用值时,操作失败,返回false
// 当期望版本号不等于当前版本号时,操作失败,返回false
// 在期望引用值和期望版本号同时等于当前值的前提下
// 当新的引用值和新的版本号同时等于当前值时,不更新,直接返回true
// 当新的引用值和新的版本号不同时等于当前值时,同时设置新的引用值和新的版本号,返回true
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp)
 
// 以原子方式设置引用的当前值为新值newReference
// 同时,以原子方式设置版本号的当前值为新值newStamp
// 新引用值和新版本号只要有一个跟当前值不一样,就进行更新
public void set(V newReference, int newStamp)
 
// 以原子方式设置版本号为新的值
// 前提:引用值保持不变
// 当期望的引用值与当前引用值不相同时,操作失败,返回fasle
// 当期望的引用值与当前引用值相同时,操作成功,返回true
public boolean attemptStamp(V expectedReference, int newStamp)
 
// 使用`sun.misc.Unsafe`类原子地交换两个对象
private boolean casPair(Pair<V> cmp, Pair<V> val)
​
AtomicMarkableReference<V> 状态戳简化

AtomicMarkableReference与AtomicStampedReference的区别是Pair内部类维护的类型不同。

类似于上面的版本号,但是主要是解决一次性问题

解决是否修改过,它的定义就是将状态戳简化boolean也就是true或者falset,类似于一次性筷子

    static AtomicMarkableReference<Integer> 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);
            //暂停1秒钟线程,等待后面的T2线程和我拿到一样的模式flag标识,都是false
            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);
            //这里停2秒,让t1先修改,然后t2试着修改
            try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
            boolean t2Result = markableReference.compareAndSet(100, 1000, marked, !marked);
            System.out.println(Thread.currentThread().getName()+"\t"+"t2线程result--"+t2Result);
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.isMarked());
            System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference());
 
        },"t2").start();
    }
}
​

运行结果:

//t1 默认标识false //t2 默认标识false //t2 t2线程result--false //t2 true //t2 1000

对象的属性修改原子类

使用要求:

  • 更新的对象属性必须使用public volatile修饰符
  • 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
  • 属性的修饰符(public/protected/default/private)要保证当前操作对该属性可以直接进行,比如当我们用private volatile int age 时就会报错,因为private修饰时,外部无法访问也无法修改。
  • 只能是实例变量,不能是类变量,也就是说不能加static关键字。
  • 只能是可修改变量,不能使final变量,因为final的语义就是不可修改。
  • 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段不能修改其包装类型(Integer/Long) 。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。

修改引用类型:

package 原子类;
​
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
​
class yingyongDemo {
    public static void main(String[] args) {
        MyVar myVar = new MyVar();
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                myVar.init();
            }, String.valueOf(i)).start();
​
        }
    }
​
    public static class MyVar {
        public volatile Boolean isInit = Boolean.FALSE;
        AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater =
                AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
​
        public void init() {
            if (referenceFieldUpdater.compareAndSet(this, Boolean.FALSE, Boolean.TRUE)) {
                System.out.println(Thread.currentThread().getName() + "\t" + "-----start init,needs 3 seconds");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + "-----over init");
            } else {
                System.out.println(Thread.currentThread().getName() + "\t" + "抱歉,已经有其他线程进行了初始化");
            }
        }
    }
}

1 -----start init,needs 3 seconds 5 抱歉,已经有其他线程进行了初始化 4 抱歉,已经有其他线程进行了初始化 2 抱歉,已经有其他线程进行了初始化 3 抱歉,已经有其他线程进行了初始化 1 -----over init

Adder累加器

LogAdder比AtomicLog的区别

  • java8引入的,相比较是一个比较新的类

  • 高并发下LogAdder比AtomicLog效率高,不过本质是空间换时间

  • 竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性

  • LongAdder适合的场景是统计求和计数的场景,而且LongAdder基本只提供了add方法,而AtomicLong还具有cas方法

    多线程下AtomicLong的性能,有20个线程对同一个AtomicLong累加(由于竞争很激烈,每一次加法,都要flush和refresh,导致很耗费资源)

  • 在内部,这个LongAdder的实现原理和AtomicLong是不同的,刚才的AtomicLong的实现原理是,每一次加法都需要做同步,所以在高并发的时候会导致冲突比较多,也就降低了效率

  • 而此时的LongAdder,每个线程都有一个计数器,仅用来在自己的线程内计算,这样一来就不会和其他线程的计数器干扰

  • 如下图,第一个线程的计数器的数值,也就是ctr’,为1的时候,可能线程2的计数器ctr’’的数值已经是3了,他们之间并不存在竞争关系,所以在加和的过程中,根本不需要同步机制,也不需要刚才的flush和reflush。这里没有一个公共的counter来给所有线程统一计数

  • LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计数:

    base变量:竞争不激烈,直接累加到该变量上

    Cell[]:竞争激烈,各个线程分散累加到自己的槽Cell[i]中,

总的来说: LongAdder的基本思路就是分散热点 ,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。sum源码:

 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;
    }
​

sum()会将所有Cell数组中的value和base累加作为返回值, 核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点 。

一句话总结longAdder原理

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

名称原理场景缺陷
AtomicLongCAS + 自旋低并发下的全局计算,AlomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。可允许一些性能损耗,要求高精度时可使用。AtomicLong是多个线程针对单个热点值value进行原子操作高并发后性能急剧下降。(N个线程CAS操作修改线程的值,每次只有一个成功过,其它N-1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,占用大量CPU)
LongAdderCAS+Base+Cell数组分散,通过空间换时间分散了热点数据高并发下的全局计算,当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用。LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作sum求和后还有计算线程修改结果的话,最后结果不够准确

Accumulator累加器

public class LongAccumulatorDemo {
    public static void main(String[] args) {
        //需要传入累加的函数
        LongAccumulator accumulator = new LongAccumulator((x,y)->x+y,0);
        ExecutorService executor = Executors.newFixedThreadPool(8);
        IntStream.range(1,10).forEach(i->executor.submit(()->accumulator.accumulate(i)));
        executor.shutdown();
        while (!executor.isTerminated()){
​
        }
        System.out.println(accumulator.getThenReset()); //45
    }
}
​

 

使用场景:

① 适用于需要大量计算,并且需要并行计算的场景,如果不需要并行计算,可用for循环解决问题,用了Accumulator累加器可利用多核同时计算,提供效率

② 计算的顺序不能成为瓶颈,线程1可能在线程5之后运行,也可能在之前运行,不影响最终结果

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pritunl搭建OpenVPN服务器详细流程,快速实现公网远程连接!

文章目录 前言1.环境安装2.开始安装3.访问测试4.创建连接5.局域网测试连接6.安装cpolar7.配置固定公网访问地址8.远程连接测试 前言 Pritunl是一款免费开源的 VPN 平台软件&#xff08;但使用的不是标准的开源许可证&#xff0c;用户受到很多限制&#xff09;。这是一种简单有…

GO学习之 通道(nil Channel妙用)

GO系列 1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 10、GO学习之 网络通信(Net/Htt…

STM32F4VGT6-DISCOVERY:uart1驱动

对于这款板子&#xff0c;官方并没有提供串口例程&#xff0c;只能自行添加。 一、PA9/PA10复用成串口1功能不可用 驱动测试代码如下&#xff1a; main.c: #include "main.h" #include <stdio.h>void usart1_init(void) {GPIO_InitTypeDef GPIO_InitStruct…

前端 : 用html ,css,js写一个你画我猜的游戏

1.HTML&#xff1a; <body><div id "content"><div id "box1">计时器</div><div id"box"><div id "top"><div id "box-top-left">第几题:</div><div id "box…

linux-磁盘应用

目录 一、磁盘内容简述 1、一些基本概念 2、分区简述 3、常见文件系统 4、linux硬盘文件 二、对linux系统进行分区 1、用fdisk进行分区 2、用parted进行分区 一、磁盘内容简述 1、一些基本概念 - 扇区大小&#xff1a;512Btyes&#xff0c;0.5KB - 磁盘最小存储单位&…

小黑子—spring:第一章 Bean基础

spring入门1.0 一 小黑子对spring基础进行概述1.1 spring导论1.2 传统Javaweb开发困惑及解决方法1.3 三大的思想提出1.3.1 IOC入门案例1.3.2 DI入门案例 1.4 框架概念1.5 初识spring1.5.1 Spring Framework 1.6 BeanFactory快速入门1.7 ApplicationContext快速入门1.8 BeanFact…

python之计算平面点集的的面积

在当今数据驱动的世界中&#xff0c;计算平面点集的最小外接轮廓面积被广泛应用于各种实际场景中。它是一项重要而魅力十足的任务&#xff0c;旨在找到一个最小的矩形或多边形区域&#xff0c;能够完全包围给定的离散点集。这个看似简单的问题背后隐藏着许多挑战&#xff0c;需…

HTML5+CSS3+Vue小实例:路飞出海的动画特效

实例:路飞出海的动画特效 技术栈:HTML+CSS+Vue 效果: 源码: 【HTML】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport" content=&…

windows下OOM排查

如下有一段代码 package com.lm.demo.arthas.controller;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;import java.util.A…

车载音频项目

加我微信hezkz17进数字音频系统研究开发交流答疑群(课题组) ー 1&#xff0e;负责此项目的音频链路的设计及其实现 在ADSP21375上实现音频链路的处理。如噪声门&#xff0c;压限器&#xff0c;高低通&#xff0c;PEQ、各种效果等。 2&#xff0e;负责DSP与MCU端SPI协议实现。M…

Python文件——使用Python读取txt文件

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 本文专栏&#xff1a;Python专栏 专栏介绍&#xff1a;本专栏为免费专栏&#xff0c;并且会持续更新python基础知识&#xff0c;欢迎各位订阅关注. 目录 一、文件的编码 1. 什么是编码 2. 常见的编码 二、P…

【纯离线】Ubuntu离线安装ntp时间同步服务

Ubuntu离线安装ntp服务 准备阶段&#xff1a;下载安装包 apt-get download ntp apt-get download ntpdate 一、服务端( 192.166.6.xx) 1、环境准备 先判断是否已安装 systemd-timesyncd systemctl is-active systemd-timesyncd 如果返回结果是 active&#xff0c;则表示…

文件夹批量改名:如何在文件夹名左边添加递增的自动编号

在文件管理的过程中&#xff0c;我们有时需要对文件夹进行重命名&#xff0c;使其更具区分度和可读性。为了实现这一目标&#xff0c;我们可以采用在文件夹名左边添加递增的自动编号的方法。本文将介绍云炫文件管理器如何进行文件夹批量改名&#xff0c;以在文件夹名左边添加递…

mathtype7.4破解永久激活码

MathType(数学公式编辑器)是由Design Science公司研发的一款专业的数学公式编辑工具。MathType功能非常强大&#xff0c;尤其适用于专门研究数学领域的人群使用。使用MathType让你在输入数学公式的时候能够更加的得心应手&#xff0c;各种复杂的运算符号也不在话下。 MathType最…

Vue3.0插槽

用法&#xff1a; 父组件App.vue <template><div><!--将html代码插入到子组件中带默认名称的插槽中--><AChild><!--这段html会插入到AChild组件中<slot></slot>插槽中--><!-- 注意&#xff1a;写在父组件中的html代码只能在父组…

百度网盘使用指南

文章目录 备份篇手机文件备份电脑文件备份 查找篇移动端PC端 文件操作文件解压文件扫描PDF工具图片工具音频操作 备份篇 手机文件备份 在百度网盘APP种点击 我的–设置–自动备份设置 里边有相册备份, 文档备份, 微信文件备份, 手机通讯录, 短信, 通话备份等功能 电脑文件备…

目标检测类项目数据集汇总

一、玩手机数据集及检测 玩手机数据集下载地址分享: https://download.csdn.net/download/qq_34717531/19870205 二、狗的数据集及检测 狗目标检测数据集下载地址分享:https://download.csdn.net/download/qq_34717531/20813390 三、猫数据集及检测 猫数据集下载地址分享: ht…

回归算法|长短期记忆网络LSTM及其优化实现

本期文章将介绍LSTM的原理及其优化实现 序列数据有一个特点&#xff0c;即“没有曾经的过去则不存在当前的现状”&#xff0c;这类数据以时间为纽带&#xff0c;将无数个历史事件串联&#xff0c;构成了当前状态&#xff0c;这种时间构筑起来的事件前后依赖关系称其为时间依赖&…

c++设计模式三:工厂模式

本文通过一个例子简单介绍简单工厂模式、工厂模式和抽象工厂模式。 1.简单工厂&#xff08;静态&#xff09; 假如我想换个手机&#xff0c;换什么手机呢&#xff1f;可以考虑苹果或者华为手机&#xff0c;那我们用简单工厂模式来实现这个功能&#xff1a; 我们关注的产品是手…

基于群居蜘蛛算法的无人机航迹规划

基于群居蜘蛛算法的无人机航迹规划 文章目录 基于群居蜘蛛算法的无人机航迹规划1.群居蜘蛛搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用群居蜘蛛算法来优化无人机航迹规划。 …