java原子类-Atomic

news2024/12/23 0:32:36

什么是原子类?

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.coloradmin.cn/o/1144743.html

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

相关文章

Redis(06)| 数据结构-整数集合

整数集合是 Set 对象的底层实现之一。当一个 Set 对象只包含整数值元素&#xff0c;并且元素数量不大时&#xff0c;就会使用整数集这个数据结构作为底层实现。 整数集合结构设计 整数集合本质上是一块连续内存空间&#xff0c;它的结构定义如下&#xff1a; typedef struct…

【黑马程序员】Springboot2 学习笔记

课程地址 1. Springboot parent和starter区别 parent&#xff1a;开发Springboot项目需要继承spring-boot-starter-parent&#xff0c;其中定义了若干个依赖管理&#xff08;坐标版本号&#xff09;&#xff0c;避免依赖版本冲突&#xff1b;starter&#xff1a;开发Springboo…

提升演讲口才,助青少年踏上成功之路

提升演讲口才&#xff0c;助青少年踏上成功之路 引言&#xff1a; 青少年时期是一个人成长发展的关键阶段&#xff0c;而演讲口才的培养不仅可以帮助他们在学业和职业上取得成功&#xff0c;还能帮助他们塑造自信、提升沟通能力&#xff0c;并在社交场合中脱颖而出。本文将探讨…

springboot在线招聘系统

springboot在线招聘管理系统&#xff0c;java在线招聘管理系统&#xff0c;在线招聘管理系统 运行环境&#xff1a; JAVA版本&#xff1a;JDK1.8 IDE类型&#xff1a;IDEA、Eclipse都可运行 数据库类型&#xff1a;MySql&#xff08;8.x版本都可&#xff09; 硬件环境&#xf…

GDB调试-链接器

GDB&#xff08;GNU Debugger&#xff09;是一个强大的命令行调试工具&#xff0c;用于调试C、C等编程语言的程序。以下是一些常用的GDB调试命令&#xff1a; 一、启动 GDB&#xff1a;打开终端并输入以下命令来启动GDB&#xff0c;并将可执行文件作为参数 gdb ld-new 二、运…

【源码解析】Spring源码解读-beanFactory和Bean的后置处理器流程

上一篇文章&#xff0c;我们详细介绍了spring是如何通过加载xml配置文件&#xff0c;将beanfactry创建成功的&#xff0c;接着核心流程&#xff0c;我们继续说下beanFactory和Bean的后置处理流程。 //留给子类的模板方法&#xff0c;允许子类继续对工厂执行一些处理&#xff1b…

二、【海报合成的创意】

文章目录 多分析他人海报&#xff0c;逐渐学会生成自己的创意关键词创意寻找素材寻找创意灵感的网站 那么如何快速生成创意&#xff1f; 多分析他人海报&#xff0c;逐渐学会生成自己的创意 关键词创意 从上图中我们可以看到&#xff0c;该海报中主要突出的主体是耳机。突出的…

【Git企业开发】第一节.Git 的分支管理

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;Git企业级开发 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff0…

java后端返回数据给前端时去除值为空或NULL的属性、忽略某些属性

目录 一、使用场景 二、环境准备 1、引入依赖 2、实体类 三、示例 1、不返回空值 (1)方式 (2)测试 (3)说明 2、不返回部分属性 (1)方式 (2)测试 一、使用场景 在开发过程中&#xff0c;有时候需要将后端数据返回前端&#xff0c;此时有些数据为空属性不需要返回&…

SSM度假村管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 度假村管理系统是一套完善的信息系统&#xff0c;结合SSM框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要 采用B/S模式开发…

[LaTeX] [数学符号] \mathbb{1}的各种替代方案:解决在 LaTeX 中输入黑板粗体的数字

[LaTeX] [数学符号] \mathbb{1}的各种替代方案&#xff1a;解决在 LaTeX 中输入黑板粗体的数字_latex mathbb-CSDN博客文章浏览阅读5w次&#xff0c;点赞36次&#xff0c;收藏80次。本文介绍如何在 LaTeX 中输入黑板粗体的数字。_latex mathbbhttps://blog.csdn.net/xovee/arti…

FRP内网穿透(待续)

FRP内网穿透技术原理&#xff1a; FRP可以将内网服务主机以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 1.GITHUB下载FRP 2.云服务器安装FRP修改配置 3.本地计算机安装FRP修改配置

R-FCN: Object Detection via Region-based Fully Convolutional Networks(2016.6)

文章目录 AbstractIntroduction当前最先进目标检测存在的问题针对上述问题&#xff0c;我们提出... Our approachOverviewBackbone architecturePosition-sensitive score maps & Position-sensitive RoI pooling Related WorkExperimentsConclusion 原文链接 源代码 Abstr…

iOS的应用生命周期以及应用界面

在iOS的原生开发中&#xff0c;我们需要特别关注两个东西&#xff1a;AppDelegate和ViewController。我们主要的编码工作就是在AppDelegate和ViewControlle这两个类中进行的。它们的类图如下图所示&#xff1a; AppDelegate是应用程序委托对象&#xff0c;它继承了UIResponder类…

业务设计——责任链验证推翻 if-else 炼狱

责任链模式 1. 什么是责任链模式 在责任链模式中&#xff0c;多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理&#xff0c;然后再把请求传递给 B 处理器&#xff0c;B 处理器处理完后再传递给 C 处理器&#xff0c;以此类推&#xff0c;形成一个链条&#xff0c;链…

Proteus仿真--左右来回流水灯仿真(仿真文件+程序)

本文主要介绍基于51单片机的流水灯仿真&#xff08;完整仿真源文件及代码见文末链接&#xff09; 仿真运行视频 Proteus仿真--基于51单片机的流水灯仿真&#xff08;左右来回&#xff09; 附完整Proteus仿真资料代码资料 百度网盘链接: https://pan.baidu.com/s/1pS1rHGOhwYgP…

springboot+vue基于协同过滤算法的私人诊所管理系统的设计与实现【内含源码+文档+部署教程】

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

MySQL Server 5.5 软件和安装配置教程

MySQL 5.5.58&#xff08;32/64位&#xff09;下载链接&#xff1a; 百度网盘&#xff1a;百度网盘 请输入提取码 提取密码&#xff1a;7act 软件简介&#xff1a; MySQL 是由瑞典MySQL AB 公司开发一个关系型数据库管理系统&#xff0c;目前属于 Oracle 旗下产品。MySQL 是最…

15KW永磁同步电动机设计

摘 要 在我们日常生活中&#xff0c;永磁同步电机随处可见&#xff0c;因为其相比其他电机而言结构相对简单&#xff0c;运行稳定且便于维修等优势&#xff0c;最重要的是永磁同步电机在调速方面具有很好的优势。随着自动控制技术和微电子技术的不断革新&#xff0c;目前的技术…

她从家乡自贡起步,努力奋斗进京任职

读罢“北京文博”于2023年10月14日发表的新闻报道《文明互鉴 文明共兴——2023国际城市媒体北京论坛在京举行》&#xff0c;心中甚为欣慰。因为当年从笔者家乡走出去的70后女青年吴旭&#xff0c;现在令人惊喜地已经成长为中华全国新闻工作者协会党组成员、中宣部对外推广局局长…