线程安全性
当多线程拜访某个类时,不论运行环境采纳何种调度形式或者这些过程将如何交替执行,并且在主调代码中不须要任何的同步或者协同,这个类都能体现出正确的行为,那么这个类就是线程平安的.
原子性
提供互斥拜访,同一时刻只有一个线程对它进行拜访.
Atomic包
位于java.util.concurrent.atomic
,AtomicXXX : CAS、Unsafe.compareAndSwapXXX
CAS(Compare and swap)
比拟和替换是设计并发算法用的的一项技术,比拟和替换是用一个期望值和一个变量的以后值进行比拟,如果变量的值和期望值相等,那么就用一个新值替换变量的值.
案例
线程平安
package com.keytech.task;import java.util.concurrent.Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.atomic.LongAdder;/** * @className: AtomicTest * @description: TODO 类形容 * @author: mac * @date: 2020/12/27 **/public class AtomicTest { private static Integer clientTotal=5000; private static Integer threadTotal=200; private static AtomicInteger count=new AtomicInteger(0); public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore= new Semaphore(threadTotal); for (int i = 0; i <clientTotal ; i++) { executorService.execute(()->{ try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } }); } executorService.shutdown(); System.out.println("count:"+count); } private static void update(){ count.incrementAndGet(); }}
getAndAddInt源码
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;}
compareAndSwapInt(this, stateOffset, expect, update)
这个办法的作用就是通过cas技术来预测stateOffset变量的初始值是否是expect,如果是,那么就把stateOffset变量的值变成update,如果不是,那么就始终自旋转,始终到stateOffset变量的初始值是expect,而后在在批改stateOffset变量的值变成update
LongAddr
线程平安
package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.atomic.LongAdder;/** * @className: LongAddrTest * @description: TODO 类形容 * @author: mac * @date: 2020/12/28 **/public class LongAddrTest { private static Integer clientTotal=5000; private static Integer threadTotal=200; private static LongAdder count=new LongAdder(); public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore=new Semaphore(threadTotal); for (int i = 0; i < clientTotal; i++) { try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } } executorService.shutdown(); System.out.println("count"+count); } private static void update(){ count.increment(); }}
AtomicLong
线程平安
package com.keytech.task;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import java.util.concurrent.atomic.AtomicLong;/** * @className: AtomicLongTest * @description: TODO 类形容 * @author: mac * @date: 2020/12/28 **/public class AtomicLongTest { private static Integer clientTotal=5000; private static Integer threadTotal=200; private static AtomicLong count=new AtomicLong(); public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); Semaphore semaphore=new Semaphore(threadTotal); for (int i = 0; i < clientTotal; i++) { try{ semaphore.tryAcquire(); update(); semaphore.release(); }catch (Exception e){ e.printStackTrace(); } } executorService.shutdown(); System.out.println("count"+count); } private static void update(){ count.incrementAndGet(); }}
LongAddr与AtomicLong的区别
AtomicLong的原理是依附底层的cas来保障原子性的更新数据,在要增加或者缩小的时候,会应用死循环不断地cas到特定的值,从而达到更新数据的目标。如果竞争不强烈,批改胜利几率很高,否则失败概率很高,在失败几率很高的状况下,这些原子操作就会进行屡次的循环操作尝试,因而性能会受到影响。
对于一般类型的Long和Doubble变量,JVM容许将64位的读操作或写操作拆成两个三十二位的操作。LongAdder则是外部保护一个Cells数组,每个Cell外面有一个初始值为0的long型变量,在等同并发量的状况下,抢夺单个变量的线程会缩小,这是变相的缩小了抢夺共享资源的并发量,另外多个线程在抢夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其余原子变量的锁,最初当获取以后值时候是把所有变量的值累加后再加上base的值返回的。
LongAdder的外围是将热点数据拆散,比如说它能够将AtomicLong外部外围数据value拆散成一个数组,每个线程拜访时,通过hash等算法,映射到其中一个数字进行计数,最终的计数后果则会这个数据的求和累加,其中热点数据value会被拆散成多个cell,每个cell单独保护外部的值,以后对象理论值为所有cell累计合成,这样的话,热点就进行了无效的拆散,并进步了并行度。
LongAdder在AtomicLong的根底上将单点的更新压力扩散到各个节点,在低并发的时候通过对base的间接更新能够很好的保障和AtomicLong的性能根本保持一致,而在高并发的时候通过扩散进步了性能。毛病是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。
LongAddr与AtomicLong的应用场景
理论应用中,在解决高并发时,能够优先应用LongAdder,而不是持续应用AtomicLong,当然,在线程竞争很低的状况下,应用AtomicLong更简略更理论一些,并且效率会高些。其余状况下,比方序列号生成,这种状况下须要精确的数值,全局惟一的AtomicLong才是正确的抉择,而不是LongAdder