当前位置: 首页 > news >正文

java高性能并发计数器之巅峰对决

并发计数器各个方案介绍

  方案概述

  1. jdk5提供的原子更新长整型类 AtomicLong

  2. synchronized

  3. jdk8提供的 LongAdder 【单机推荐】

  4. Redisson分布式累加器【分布式推荐】

  方案介绍

  jdk5提供的原子更新长整型类 AtomicLong

  在JDK1.5开始就新增了并发的Integer/Long的操作工具类AtomicInteger和AtomicLong。

  AtomicLong 利用底层操作系统的CAS来保证原子性,在一个死循环内不断执行CAS操作,直到操作成功。不过,CAS操作的一个问题是在并发量比较大的时候,可能很多次的执行CAS操作都不成功,这样性能就受到较大影响。

  示例代码

AtomicLong value =newAtomicLong(0);//定义incrementAndGet();//递增1
```

  synchronized

  synchronized是一个重量级锁,主要是因为线程竞争锁会引起操作系统用户态和内核态切换,浪费资源效率不高,在jdk1.5之前,synchronized没有做任何优化,但在jdk1.6做了性能优化,它会经历偏向锁,轻量级锁,最后才到重量级锁这个过程,在性能方面有了很大的提升,在jdk1.7的ConcurrentHashMap是基于ReentrantLock的实现了锁,但在jdk1.8之后又替换成了synchronized,就从这一点可以看出JVM团队对synchronized的性能还是挺有信心的。下面我们分别来介绍下无锁,偏向锁,轻量级锁,重量级锁。

  jdk8提供的 LongAdder 【单机推荐】

  在JDK8中又新增了LongAdder,这是一个针对Long类型的数据的操作工具类。

  那我们知道,在ConcurrentHashMap中,对Map分割成多个segment,这样多个Segment的操作就可以并行执行,从而可以提高性能。在JDK8中,LongAdder与ConcurrentHashMap类似,将内部操作数据value分离成一个Cell数组,每个线程访问时,通过Hash等算法映射到其中一个Cell上。

  计算最终的数据结果,则是各个Cell数组的累计求和。

  LongAddr常用api方法

add():  //增加指定的数值;increament(): //增加1;decrement(): //减少1;intValue();//intValue();/floatValue()/doubleValue():得到最终计数后的结果sum()://求和,得到最终计数结果sumThenReset()://求和得到最终计数结果,并重置value。
```

  Redisson分布式累加器【分布式推荐】

  基于Redis的Redisson分布式整长型累加器(LongAdder)采用了与java.util.concurrent.atomic.LongAdder类似的接口。通过利用客户端内置的LongAdder对象,为分布式环境下递增和递减操作提供了很高得性能。据统计其性能最高比分布式AtomicLong对象快 10000 倍以上。

RLongAddr itheimaLongAddr = redission.getLongAddr("itheimaLongAddr");
itheimaLongAddr.add(100);//添加指定数量
itheimaLongAddr.increment();//递增1
itheimaLongAddr.increment();//递减1
itheimaLongAddr.sum();//聚合求和
```

  基于Redis的Redisson分布式双精度浮点累加器(DoubleAdder)采用了与java.util.concurrent.atomic.DoubleAdder类似的接口。通过利用客户端内置的DoubleAdder对象,为分布式环境下递增和递减操作提供了很高得性能。据统计其性能最高比分布式AtomicDouble对象快 12000 倍。

  示例代码

RLongDouble itheimaDouble = redission.getLongDouble("itheimaLongDouble");
itheimaDouble.add(100);//添加指定数量
itheimaDouble.increment();//递增1
itheimaDouble.increment();//递减1
itheimaDouble.sum();//聚合求和
```

  以上【整长型累加器】和【双精度浮点累加器】完美适用于分布式统计计量场景。

  各个方案性能测试

  测试代码

```
packagecom.itheima._01性能比较;importorg.junit.Test;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.atomic.AtomicLong;importjava.util.concurrent.atomic.LongAdder;/*** @author 黑马程序员*/publicclassCountTest{privateint count =0;@TestpublicvoidstartCompare(){compareDetail(1,100*10000);compareDetail(20,100*10000);compareDetail(30,100*10000);compareDetail(40,100*10000);compareDetail(100,100*10000);}/*** @paramthreadCount 线程数* @paramtimes 每个线程增加的次数*/publicvoidcompareDetail(int threadCount,int times){try{System.out.println(String.format("threadCount: %s, times: %s", threadCount, times));long start =System.currentTimeMillis();testSynchronized(threadCount, times);System.out.println("testSynchronized cost: "+(System.currentTimeMillis()- start));start =System.currentTimeMillis();testAtomicLong(threadCount, times);System.out.println("testAtomicLong cost: "+(System.currentTimeMillis()- start));start =System.currentTimeMillis();testLongAdder(threadCount, times);System.out.println("testLongAdder cost: "+(System.currentTimeMillis()- start));System.out.println();}catch(Exception e){e.printStackTrace();}}publicvoidtestSynchronized(int threadCount,int times)throwsInterruptedException{List<Thread> threadList =newArrayList<>();for(int i =0; i < threadCount; i++){threadList.add(newThread(()->{for(int j =0; j < times; j++){add();}}));}for(Thread thread : threadList){thread.start();}for(Thread thread : threadList){thread.join();}}publicsynchronizedvoidadd(){count++;}publicvoidtestAtomicLong(int threadCount,int times)throwsInterruptedException{AtomicLong count =newAtomicLong();List<Thread> threadList =newArrayList<>();for(int i =0; i < threadCount; i++){threadList.add(newThread(()->{for(int j =0; j < times; j++){count.incrementAndGet();}}));}for(Thread thread : threadList){thread.start();}for(Thread thread : threadList){thread.join();}}publicvoidtestLongAdder(int threadCount,int times)throwsInterruptedException{LongAdder count =newLongAdder();List<Thread> threadList =newArrayList<>();for(int i =0; i < threadCount; i++){threadList.add(newThread(()->{for(int j =0; j < times; j++){count.increment();}}));}for(Thread thread : threadList){thread.start();}for(Thread thread : threadList){thread.join();}}}
```

  运行结果

threadCount: 1, times: 1000000
testSynchronized cost: 69
testAtomicLong cost: 16
testLongAdder cost: 15threadCount: 20, times: 1000000
testSynchronized cost: 639
testAtomicLong cost: 457
testLongAdder cost: 59threadCount: 30, times: 1000000
testSynchronized cost: 273
testAtomicLong cost: 538
testLongAdder cost: 70threadCount: 40, times: 1000000
testSynchronized cost: 312
testAtomicLong cost: 717
testLongAdder cost: 81threadCount: 100, times: 1000000
testSynchronized cost: 719
testAtomicLong cost: 2098
testLongAdder cost: 225
```

  结论

  

  并发量比较低的时候AtomicLong优势比较明显,因为AtomicLong底层是一个乐观锁,不用阻塞线程,不断cas即可。但是在并发比较高的时候用synchronized比较有优势,因为大量线程不断cas,会导致cpu持续飙高,反而会降低效率

  LongAdder无论并发量高低,优势都比较明显。且并发量越高,优势越明显

  原理分析

  AtomicLong 实现原子操作原理

  非原子操作示例代码

packagecom.itheima._02Unsafe测试;importjava.util.ArrayList;importjava.util.List;/*** @author 黑马程序员*/publicclassTest1{privateint value =0;publicstaticvoidmain(String[] args)throwsInterruptedException{Test1 test1 =newTest1();test1.increment();System.out.println("期待值:"+100*100+",最终结果值:"+ test1.value);//结果,期待值:10000,最终结果值:xxxx}privatevoidincrement()throwsInterruptedException{List<Thread> list =newArrayList<>();//启动100个线程,每个线程对value进行累加100次for(int i =0; i <100; i++){Thread t =newThread(()->{for(int j =0; j <100; j++){value++;}});list.add(t);t.start();}//保证所有线程运行完成for(Thread thread : list){thread.join();}}}
```

  运行效果

  结论

  > 可以发现输出的结果值错误,这是因为 `value++` 不是一个原子操作,它将 `value++` 拆分成了 3 个步骤 `load、add、store`,多线程并发有可能上一个线程 add 过后还没有 store 下一个线程又执行了 load 了这种重复造成得到的结果可能比最终值要小。

  AtomicLong是JDK1.5提供的原子操作示例代码

packagecom.itheima._03AtomicLong的CAS原子操作示例;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.atomic.AtomicInteger;importjava.util.concurrent.atomic.AtomicLong;/*** @author 黑马程序员*/publicclassTest2{privateAtomicLong value =newAtomicLong(0);publicstaticvoidmain(String[] args)throwsInterruptedException{Test2 test1 =newTest2();test1.increment();System.out.println("期待值:"+100*100+",最终结果值:"+ test1.value);//结果,期待值:10000,最终结果值:10000}privatevoidincrement()throwsInterruptedException{List<Thread> list =newArrayList<>();//启动100个线程,每个线程对value进行累加100次for(int i =0; i <100; i++){Thread t =newThread(()->{for(int j =0; j <100; j++){value.incrementAndGet();}});list.add(t);t.start();}//保证所有线程运行完成for(Thread thread : list){thread.join();}}}
```

  运行效果

  AtomicLong CAS原理介绍

  1.使用volatile保证内存可见性,获取主存中最新的操作数据

  2.使用CAS(Compare-And-Swap)操作保证数据原子性

  CAS算法是jdk对并发操作共享数据的支持,包含了3个操作数

  第一个操作数:内存值value(V)

  第二个操作数:预估值expect(O)

  第三个操作数:更新值new(N)

  含义:CAS比较交换的过程可以通俗的理解为CAS(V,O,N),包含三个值分别为:V 内存地址(主存)存放的实际值;O 预期的值(旧值);N 更新的新值。当V和O相同时,也就是说旧值和内存中实际的值相同表明该值没有被其他线程更改过,即该旧值O就是目前来说最新的值了,自然而然可以将新值N赋值给V;当V和O不相同时,会一致循环下去直至修改成功。

  AtomicLong底层CAS实现原子操作原理

  查看incrementAndGet()方法源码

publicfinallongincrementAndGet(){return unsafe.getAndAddLong(this, valueOffset,1L)+1L;}
```getAndAddLong方法源码```java
publicfinallonggetAndAddLong(Object var1,long var2,long var4){long var6;do{var6 =this.getLongVolatile(var1, var2);}while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));return var6;}
```

  > 这里是一个循环CAS操作

  compareAndSwapLong方法源码

publicfinalnativebooleancompareAndSwapLong(Object var1,long var2,long var4,long var6);
```

  我们发现调用的是 native 的 `unsafe.compareAndSwapLong(Object obj, long valueOffset, Long expect, Long update)`,我们翻看 Hotspot 源码发现在 unsafe.cpp 中定义了这样一段代码

  > Unsafe中基本都是调用native方法,那么就需要去JVM里面找对应的实现。

  >

  > 到`http://hg.openjdk.java.net/` 进行一步步选择下载对应的hotspot版本,我这里下载的是`http://hg.openjdk.java.net/jdk8u/jdk8u60/hotspot/archive/tip.tar.gz`,

  >

  > 然后解hotspot目录,发现 `\src\share\vm\prims\unsafe.cpp`,这个就是对应jvm相关的c++实现类了。

  >

  > 比如我们对CAS部分的实现很感兴趣,就可以在该文件中搜索compareAndSwapInt,此时可以看到对应的JNI方法为`Unsafe_CompareAndSwapInt`

UNSAFE_ENTRY(jboolean,Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))UnsafeWrapper("Unsafe_CompareAndSwapLong");Handle p(THREAD,JNIHandles::resolve(obj));jlong* addr =(jlong*)(index_oop_from_field_offset_long(p(), offset));#ifdefSUPPORTS_NATIVE_CX8return(jlong)(Atomic::cmpxchg(x, addr, e))== e;#elseif(VM_Version::supports_cx8())return(jlong)(Atomic::cmpxchg(x, addr, e))== e;else{jboolean success =false;MutexLockerEx mu(UnsafeJlong_lock, Mutex::_no_safepoint_check_flag);jlong val =Atomic::load(addr);if(val == e){Atomic::store(x, addr); success =true;}return success;}#endif
UNSAFE_END
```

  Atomic::cmpxchg c++源码

  可以看到调用了“Atomic::cmpxchg”方法,“Atomic::cmpxchg”方法在linux_x86和windows_x86的实现如下。

  linux_x86的实现:

inline jint     Atomic::cmpxchg(jint     exchange_value,volatile jint*     dest, jint     compare_value){int mp = os::is_MP();__asm__ volatile(LOCK_IF_MP(%4)"cmpxchgl %1,(%3)":"=a"(exchange_value):"r"(exchange_value),"a"(compare_value),"r"(dest),"r"(mp):"cc","memory");return exchange_value;}
```

  windows_x86的实现(c++源文件):

inline jint     Atomic::cmpxchg(jint     exchange_value,volatile jint*     dest, jint     compare_value){// alternative for InterlockedCompareExchangeint mp = os::is_MP();__asm {mov edx, destmov ecx, exchange_valuemov eax, compare_valueLOCK_IF_MP(mp)cmpxchg dword ptr [edx], ecx}}
```

  Atomic::cmpxchg方法解析:

  mp是“os::is_MP()”的返回结果,“os::is_MP()”是一个内联函数,用来判断当前系统是否为多处理器。

  如果当前系统是多处理器,该函数返回1。

  否则,返回0。

  LOCK_IF_MP(mp)会根据mp的值来决定是否为cmpxchg指令添加lock前缀。

  如果通过mp判断当前系统是多处理器(即mp值为1),则为cmpxchg指令添加lock前缀。

  否则,不加lock前缀。

  这是一种优化手段,认为单处理器的环境没有必要添加lock前缀,只有在多核情况下才会添加lock前缀,因为lock会导致性能下降。cmpxchg是汇编指令,作用是比较并交换操作数。

  > 底层会调用cmpxchg汇编指令,如果是多核处理器会加锁实现原子操作

  反汇编指令查询

  查看java程序运行的汇编指令资料

  将上图2个文件拷贝到jre\bin目录下,如下图

  配置运行参数

  ```

  -server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*

  ```

  运行Test2效果

  synchronized 实现同步操作原理

  锁对象

  java中任何一个对象都可以称为锁对象,原因在于java对象在内存中存储结构,如下图所示:

  在对象头中主要存储的主要是一些运行时的数据,如下所示:

  其中 在Mark Work中存储着该对象作为锁时的一些信息,如下所示是Mark Work中在64位系统中详细信息:

  偏向锁

  在无竞争环境中(没有并发)使用一种锁

  > 偏向锁的作用是当有线程访问同步代码或方法时,线程只需要判断对象头的Mark Word中判断一下是否有偏向锁指向线程ID.

  >

  > 偏向锁记录过程

  >

  > - 线程抢到了对象的同步锁(锁标志为01参考上图即无其他线程占用)

  > - 对象Mark World 将是否偏向标志位设置为1

  > - 记录抢到锁的线程ID

  > - 进入偏向状态

  轻量级锁

  当有另外一个线程竞争获取这个锁时,由于该锁已经是偏向锁,当发现对象头 Mark Word 中的线程 ID 不是自己的线程 ID,就会进行 CAS 操作获取锁,**如果获取成功**,直接替换 Mark Word 中的线程 ID 为自己的 ID,该锁会保持偏向锁状态;**如果获取锁失败**,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。

  - 举个例子来说明一下什么时候需要升级偏向锁

  假设A线程 持有锁 X(此时X是偏向锁) 这是有个B线程也同样用到了锁X,而B线程在检查锁对象的Mark World时发现偏向锁的线程ID已经指向了线程A。这时候就需要升级锁X为轻量级锁。轻量级锁意味着标示该资源现在处于竞争状态。

  当有其他线程想访问加了轻量级锁的资源时,会使用自旋锁优化,来进行资源访问。

  > 自旋策略

  >

  > JVM 提供了一种自旋锁,可以通过自旋方式不断尝试获取锁,从而避免线程被挂起阻塞。这是基于大多数情况下,线程持有锁的时间都不会太长,毕竟线程被挂起阻塞可能会得不偿失。

  >

  > 从 JDK1.7 开始,自旋锁默认启用,自旋次数由 JVM 设置决定,这里我不建议设置的重试次数过多,因为 CAS 重试操作意味着长时间地占用 CPU。自旋锁重试之后如果抢锁依然失败,同步锁就会升级至重量级锁,锁标志位改为 10。在这个状态下,未抢到锁的线程都会进入 Monitor,之后会被阻塞在 _WaitSet 队列中。

  重量级锁

  自旋失败,很大概率 再一次自选也是失败,因此直接升级成重量级锁,进行线程阻塞,减少cpu消耗。

  当锁升级为重量级锁后,未抢到锁的线程都会被阻塞,进入阻塞队列。

  重量级锁在高并发下性能就会变慢,因为所有没有获取锁的线程会进行阻塞等待,到获取锁的时候被唤醒,这些操作都是消耗很多资源。

  轻量级锁膨胀流程图

  LongAdder 实现原子操作原理

  LongAdder实现高并发计数实现思路

  LongAdder实现高并发的秘密就是用空间换时间,对一个值的cas操作,变成对多个值的cas操作,当获取数量的时候,对这多个值加和即可。

  测试代码

```
packagecom.itheima._04LongAddr使用测试;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.atomic.AtomicLong;importjava.util.concurrent.atomic.LongAccumulator;importjava.util.concurrent.atomic.LongAdder;importjava.util.function.LongBinaryOperator;/*** @author 黑马程序员*/publicclassTest3{privateLongAdder value =newLongAdder();//默认初始值0publicstaticvoidmain(String[] args)throwsInterruptedException{Test3 test1 =newTest3();test1.increment();System.out.println("期待值:"+100*100+",最终结果值:"+ test1.value.sum());//结果,期待值:10000,最终结果值:10000}privatevoidincrement()throwsInterruptedException{List<Thread> list =newArrayList<>();//启动100个线程,每个线程对value进行累加100次for(int i =0; i <100; i++){Thread t =newThread(()->{for(int j =0; j <100; j++){value.increment();}});list.add(t);t.start();}//保证所有线程运行完成for(Thread thread : list){thread.join();}}}
```

  源码分析

  1. 先对base变量进行cas操作,cas成功后返回

  2. 对线程获取一个hash值(调用getProbe),hash值对数组长度取模,定位到cell数组中的元素,对数组中的元素进行cas

  增加数量源码

publicvoidincrement(){add(1L);}
``````java
publicvoidadd(long x){Cell[] as;long b, v;int m;Cell a;if((as = cells)!=null||!casBase(b = base, b + x)){boolean uncontended =true;if(as ==null||(m = as.length -1)<0||(a = as[getProbe()& m])==null||!(uncontended = a.cas(v = a.value, v + x)))longAccumulate(x,null, uncontended);}}
```

  当数组不为空,并且根据线程hash值定位到数组某个下标中的元素不为空,对这个元素cas成功则直接返回,否则进入longAccumulate方法

  1. cell数组已经初始化完成,主要是在cell数组中放元素,对cell数组进行扩容等操作

  2. cell数组没有初始化,则对数组进行初始化

  3. cell数组正在初始化,这时其他线程利用cas对baseCount进行累加操作

  完整代码

finalvoidlongAccumulate(long x,LongBinaryOperator fn,boolean wasUncontended){int h;if((h =getProbe())==0){ThreadLocalRandom.current();// force initializationh =getProbe();wasUncontended =true;}boolean collide =false;// True if last slot nonemptyfor(;;){Cell[] as;Cell a;int n;long v;if((as = cells)!=null&&(n = as.length)>0){if((a = as[(n -1)& h])==null){if(cellsBusy ==0){// Try to attach new CellCell r =newCell(x);// Optimistically createif(cellsBusy ==0&&casCellsBusy()){boolean created =false;try{// Recheck under lockCell[] rs;int m, j;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;}elseif(!wasUncontended)// CAS already known to failwasUncontended =true;// Continue after rehashelseif(a.cas(v = a.value,((fn ==null)? v + x :fn.applyAsLong(v, x))))break;elseif(n >= NCPU || cells != as)collide =false;// At max size or staleelseif(!collide)collide =true;elseif(cellsBusy ==0&&casCellsBusy()){try{if(cells == as){// Expand table unless staleCell[] rs =newCell[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);}elseif(cellsBusy ==0&& cells == as &&casCellsBusy()){boolean init =false;try{// Initialize tableif(cells == as){Cell[] rs =newCell[2];rs[h &1]=newCell(x);cells = rs;init =true;}}finally{cellsBusy =0;}if(init)break;}elseif(casBase(v = base,((fn ==null)? v + x :fn.applyAsLong(v, x))))break;// Fall back on using base}}
```

  获取计算数量源码

publiclongsum(){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()返回的数量有可能并不是当前的数量,因为在调用sum()方法的过程中,可能有其他数组对base变量或者cell数组进行了改动,所以需要确保所有线程运行完再获取就是准确值

  LongAdder 的前世今生

  其实在 Jdk1.7 时代,LongAdder 还未诞生时,就有一些人想着自己去实现一个高性能的计数器了,比如一款 Java 性能监控框架 dropwizard/metrics 就做了这样事,在早期版本中,其优化手段并没有 Jdk1.8 的 LongAdder 丰富,而在 metrics 的最新版本中,其已经使用 Jdk1.8 的 LongAdder 替换掉了自己的轮子。在最后的测评中,我们将 metrics 版本的 LongAdder 也作为一个参考对象。

  应用场景

  AtomicLong等原子类的使用

  并发少竞争少(读多写少)的计数原子操作

  LongAdder 的使用

  高性能计数器的首选方案, 单体项目建议使用LongAddr,分布式环境建议使用Redisson分布式累加器

  应用场景功能:获取全局自增id值

  Synchronized与Lock的使用比较

  Synchronized 适合少量的同步并发竞争

  Lock 适合大量的同步并发竞争

  总结

  并发情况优化锁思路:

  互斥锁 -> 乐观锁 -> 锁的粒度控制

  在Java中对应的实现方式:

  ReentrantLock或者Syschronized -> CAS + Volatile -> 拆分竞争点(longAddr,分布式累加器,ConcurrentHashMap等)

  ReentrantLock或者Syschronized 在高并发时都存在获取锁等待、阻塞、唤醒等操作,所以在使用的使用注意拆分竞争点。

  AtomicLong

  1. 并发量非常高,可能导致都在不停的争抢该值,可能导致很多线程一致处于循环状态而无法更新数据,从而导致 CPU 资源的消耗过高。解决这个问题需要使用LongAdder

  2. ABA 问题,比如说上一个线程增加了某个值,又改变了某个值,然后后面的线程以为数据没有发生过变化,其实已经被改动了。解决这个问题请参考《扩展:原子更新字段类-ABA问题解决》

  synchronized

  synchronized锁升级实际上是把本来的悲观锁变成了 在一定条件下 使用无所(同样线程获取相同资源的偏向锁),以及使用乐观(自旋锁 cas)和一定条件下悲观(重量级锁)的形式。

  偏向锁:适用于单线程适用锁的情况

  轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似)

  重量级锁:适用于竞争激烈的情况

  LongAdder

  - AtomicLong :并发场景下读性能优秀,写性能急剧下降,不适合作为高性能的计数器方案。内需求量少。

  - LongAdder :并发场景下写性能优秀,读性能由于组合求值的原因,不如直接读值的方案,但由于计数器场景写多读少的缘故,整体性能在几个方案中最优,是高性能计数器的首选方案。由于 Cells 数组以及缓存行填充的缘故,占用内存较大。

  最佳方案

  高性能计数器的首选方案, 单体项目建议使用LongAddr,分布式环境建议使用Redisson分布式累加器

  应用场景功能:获取全局自增id值

相关文章:

java高性能并发计数器之巅峰对决

并发计数器各个方案介绍方案概述1. jdk5提供的原子更新长整型类 AtomicLong2. synchronized3. jdk8提供的 LongAdder 【单机推荐】4. Redisson分布式累加器【分布式推荐】方案介绍jdk5提供的原子更新长整型类 AtomicLong在JDK1.5开始就新增了并发的Integer/Long的操作工具类Ato…...

HTTPS简介

HTTPS是HTTP开启TLS传输协议&#xff0c;客户端要拿到服务端的公钥&#xff0c;用公钥加密数据后再进行传输&#xff0c;防止数据泄露后背篡改。它要解决两个问题&#xff1a;怎么保证公钥可信怎么加密数据公钥可信问题客户端从服务端获取公钥的时候&#xff0c;存在请求被拦截…...

K-means聚类

原理说明 Kmeans是一种常见的聚类算法&#xff0c;用于将相似的数据点归类到不同的群组中。Kmeans的原理如下&#xff1a; 初始化&#xff1a;Kmeans算法首先需要初始化一个用户指定数量的聚类中心点&#xff0c;通常是随机选取K个数据点作为聚类中心点。 分配&#xff1a;对…...

04-SQL基础(表管理,约束,多表连接,子查询)

本文章主要内容 1、表的管理&#xff1a;创建表&#xff0c;修改表结构&#xff0c;删除字段&#xff0c;修改字段&#xff0c;添加字段&#xff0c;删除表&#xff0c;添加表约束&#xff1b; 2、数据管理&#xff1a;新增记录&#xff0c;修改记录&#xff0c;删除记录&…...

统计学 一元线性回归

统计学 一元线性回归 回归&#xff08;Regression&#xff09;&#xff1a;假定因变量与自变量之间有某种关系&#xff0c;并把这种关系用适当的数学模型表达出来&#xff0c;利用该模型根据给定的自变量来预测因变量 线性回归&#xff1a;因变量和自变量之间是线性关系 非线…...

【软件开发】基于PyQt5开发的标注软件

这里是基于PyQt5写的面向目标检测的各类标注PC端软件系统。目前现有的labelme软件和labelImg开源软件无法满足特殊数据集的标注要求&#xff0c;而且没有标注顺序的报错提示。当然我设计的软件就会不具有适用性了&#xff08;毕竟从下面开发的软件可以明显看出来我做的基本上是…...

CSS3新特性

CSS3新特性前言css3选择器边框特性背景参考前言 css3作为css的升级版本&#xff0c;css3提供了更加丰富实用的规范。新特性有&#xff1a; css3选择器边框特性多背景图颜色与透明度多列布局与弹性盒模型布局盒子的变形过渡与动画web字体媒体查询阴影 css3选择器 css3选择器…...

35 openEuler搭建repo(yum)服务器-创建、更新本地repo源

文章目录35 openEuler搭建repo&#xff08;yum&#xff09;服务器-创建、更新本地repo源35.1 获取ISO发布包35.2 挂载ISO创建repo源35.3 创建本地repo源35.4 更新repo源35 openEuler搭建repo&#xff08;yum&#xff09;服务器-创建、更新本地repo源 使用mount挂载&#xff0c…...

【三.项目引入axios、申明全局变量、设置跨域】

根据前文《二.项目使用vue-router,引入ant-design-vue的UI框架&#xff0c;引入less》搭建好脚手架后使用 需求&#xff1a; 1.项目引入axios 2.申明全局变量 3.设置跨域 简介&#xff1a;axios本质上还是对原生XMLHttpRequest的封装&#xff0c;可用于浏览器和nodejs的HTTP客…...

启动u盘还原成普通u盘(Windows Diskpart)

使用windows系统的diskpart 命令解决系统盘恢复成普通U盘的问题&#xff1a;1. 按Windows R键打开运行窗口。在搜索框中输入“ Diskpart ”&#xff0c;然后按 Enter 键。2. 现在输入“ list disk ”并回车。3. 然后输入“ select disk X ”&#xff08;将 X 替换为可启动U盘的…...

深入理解机器学习——偏差(Bias)与方差(Variance)

分类目录&#xff1a;《深入理解机器学习》总目录 偏差&#xff08;Bias&#xff09;与方差&#xff08;Variance&#xff09;是解释学习算法泛化性能的一种重要工具。偏差方差分解试图对学习算法的期望泛化错误率进行拆解&#xff0c;我们知道&#xff0c;算法在不同训练集上学…...

分布式新闻项目实战 - 13.项目部署_持续集成(Jenkins) ^_^ 完结啦 ~

欲买桂花同载酒&#xff0c;终不似&#xff0c;少年游。 系列文章目录 项目搭建App登录及网关App文章自媒体平台&#xff08;博主后台&#xff09;自媒体文章审核延迟任务kafka及文章上下架App端文章搜索后台系统管理Long类型精度丢失问题定时计算热点文章&#xff08;xxl-Job…...

Linux c/c++技术方向分析

一、C与C介绍 1.1 说明 c语言是一门面向过程的、抽象化的通用程序设计语言&#xff0c;广泛应用于底层开发&#xff0c;如嵌入式。C语言能以简易的方式编译、处理低级存储器。是一种高效率程序设计语言。 c&#xff08;c plus plus&#xff09;是一种计算机高级程序设计语言&a…...

JavaScript 高级3 :函数进阶

JavaScript 高级3 &#xff1a;函数进阶 Date: January 19, 2023 Text: 函数的定义和调用、this、严格模式、高阶函数、闭包、递归 目标&#xff1a; 能够说出函数的多种定义和调用方式 能够说出和改变函数内部 this 的指向 能够说出严格模式的特点 能够把函数作为参数和返…...

【项目】Java树形结构集合分页,java对list集合进行分页

Java树形结构集合分页需求难点实现第一步&#xff1a;查出所有树形集合数据 &#xff08;需进行缓存处理&#xff09;selectTree 方法步骤&#xff1a;TreeUtil类&#xff1a;第二步&#xff1a;分页 GoodsCategoryController分页getGoodsCategoryTree方法步骤&#xff1a;第三…...

java.lang.IllegalArgumentException: itemView may not be null

报错截图&#xff1a;场景介绍&#xff1a;在使用recycleView 自动递增数据&#xff0c;且自动滚动到最新行&#xff1b; 当数据达到273条 时出现ANR&#xff1b;项目中 全部的列表适配器使用的三方库&#xff1a;BaseRecyclerViewAdapterHelper &#xff08;很早之前的项目&am…...

[ 攻防演练演示篇 ] 利用 shiro 反序列化漏洞获取主机权限

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…...

达人合作加持品牌布局,3.8女神玩转流量策略!

随着迅猛发展的“她经济”&#xff0c;使社区本就作为内容种草的平台&#xff0c;自带“营销基因”。在3.8女神节即将到来之际&#xff0c;如何充分利用平台女性资源优势&#xff0c;借助达人合作等手段&#xff0c;实现迅速引流&#xff0c;来为大家详细解读下。一、小红书节日…...

观点丨Fortinet谈ChatGPT火爆引发的网络安全行业剧变

FortiGuard报告安全趋势明确指出“网络攻击者已经开始尝试AI手段”&#xff0c;ChatGPT的火爆之际的猜测、探索和事实正在成为这一论断的佐证。攻守之道在AI元素的加持下也在悄然发生剧变。Fortinet认为在攻击者利用ChatGPT等AI手段进行攻击的无数可能性的本质&#xff0c;其实…...

工业企业用电损耗和降损措施研究

来自用电设备和供配电系统的电能损耗。而供配电系统的电能损耗,包括企业变配电设备、控制设备企业在不断降低生产成本,追求经济效益的情况下,进一步降低供配电系统中的电能损耗,使电气设摘要:电网电能损耗是一个涉及面很广的综合性问题,主要包括管理损耗和技术损耗两部分…...

高并发、高性能、高可用

文章目录一、高并发是什么&#xff1f;二、 高性能是什么三、 高可用什么是一、高并发是什么&#xff1f; 示例&#xff1a;高并发是现在互联网分布式框架设计必须要考虑的因素之一&#xff0c;它是可以保证系统能被同时并行处理很多请求&#xff0c;对于高并发来说&#xff0…...

剑指 Offer 62. 圆圈中最后剩下的数字

摘要 剑指 Offer 62. 圆圈中最后剩下的数字 一、约瑟夫环解析 题目中的要求可以表述为&#xff1a;给定一个长度为 n 的序列&#xff0c;每次向后数 m 个元素并删除&#xff0c;那么最终留下的是第几个元素&#xff1f;这个问题很难快速给出答案。但是同时也要看到&#xff…...

概率论小课堂:高斯分布(正确认识大概率事件)

文章目录 引言I 预备知识1.1 正态分布1.2 置信度1.3 风险II 均值、标准差和发生概率三者的关系。2.1 “三∑原则”2.2 二班成绩比一班好的可能性2.3 减小标准差引言 泊松分布描述的是概率非常小的情况下的统计规律性。学习高斯分布来正确认识大概率事件,随机变量均值的差异和偶…...

剑指 Offer 43. 1~n 整数中 1 出现的次数

摘要 剑指 Offer 43. 1&#xff5e;n 整数中 1 出现的次数 一、数学思维解析 将1~ n的个位、十位、百位、...的1出现次数相加&#xff0c;即为1出现的总次数。 设数字n是个x位数&#xff0c;记n的第i位为ni​&#xff0c;则可将n写为 nxnx−1⋯n2n1&#xff1a; 称" …...

如何成为程序员中的牛人/高手?

目录 一、牛人是怎么成为牛人的&#xff1f; 二、关于牛人的一点看法 三、让程序员与业务接壤&#xff0c;在开发团队中“升级” 四、使用低代码平台 目标效果 五、最后 祝伟大的程序员们梦想成真、码到成功&#xff01; 一、牛人是怎么成为牛人的&#xff1f; 最近在某…...

云原生时代顶流消息中间件Apache Pulsar部署实操之轻量级计算框架

文章目录Pulsar Functions(轻量级计算框架)基础定义工作流程函数运行时处理保证和订阅类型窗口函数定义窗口类型滚动窗口滑动窗口函数配置函数示例有状态函数示例窗口函数示例自定义函数开发定义原生语言接口示例Pulsar函数SDK示例Pulsar Functions(轻量级计算框架) 基础定义 …...

数据结构刷题(十九):77组合、216组合总和III

1.组合题目链接过程图&#xff1a;先从集合中取一个数&#xff0c;再依次从剩余数中取k-1个数。思路&#xff1a;回溯算法。使用回溯三部曲进行解题&#xff1a;递归函数的返回值以及参数&#xff1a;n&#xff0c;k&#xff0c;startIndex(记录每次循环集合从哪里开始遍历的位…...

PyQt 做美*女GIF设置桌面,每天都很爱~

人生苦短&#xff0c;我用python 要说程序员工作的最大压力不是来自于工作本身&#xff0c; 而是来自于需要不断学习才能更好地完成工作&#xff0c; 因为程序员工作中面对的编程语言是在不断更新的&#xff0c; 同时还要学习熟悉其他语言来提升竞争力… 好了&#xff0c;学习…...

[渗透测试笔记] 54.日薪2k的蓝队hw中级定级必备笔记系列篇3之域渗透黄金票据和白银票据

前文链接 [渗透测试笔记] 52.告别初级,日薪2k的蓝队hw中级定级必备笔记 [渗透测试笔记] 53.日薪2k的蓝队hw中级定级必备笔记2 文章目录 Kerberos认证协议NTLM认证协议Kerberos和NTLM比较黄金票据原理黄金票据条件复现过程白银票据原理白银票据条件复现过程黄金票据和白银票据…...

【异常】Spring Cloud Gateway网关自定义过滤器无法获取到请求体body的内容?不存在的!

一、需求说明 项目要使用到网关SpringCloud Gateway进行验签,现在定义了一个过滤器ValidateSignFilter, 我希望,所以过网关SpringCloud Gateway的请求,都能够校验一下请求头,看看是否有Sign这个字段放在请求头中。 二、异常说明 但是,我遇到了SpringCloud Gateway网关…...