Day296.原子类 -Juc
Posted 阿昌喜欢吃黄桃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day296.原子类 -Juc相关的知识,希望对你有一定的参考价值。
原子类
一、什么是原子类&作用
-
不可分割
-
一个操作是
不可中断
的,即便是多线程的情况下也可以保证 -
java.util.concurrent.
atomic
-
作用: 类似与锁,为保证并发情况下
线程安全
。原子类相比锁更具有优势
-
粒度更细:
原子变量可以把竞争范围缩小到变量级别,这是我们可以获得的最细粒度的情况,通常锁的粒度都要比原子变量的粒度大
-
效率更高:
通常,使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况
-
二、原子类纵览
三、Atomic*基本类型原子类
包含:AtomicInteger、AtomicLong、AtomicBoolean
以AtomicInteger为例子
1、常用方法
-
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)
-
代码演示: 原子类和普通类的对比
/******
@author 阿昌
@create 2021-06-12 18:04
*******
* 演示AtomicInteger的基本用法,并对比非原子类的线程安全问题
*/
public class AtomicIntegerDemo1 implements Runnable {
private static final AtomicInteger atomicInteger = new AtomicInteger();
//原子类型自增
public void atomicIncrement(){
atomicInteger.getAndIncrement();
}
private static volatile int basicCount = 0;
//普通类型自增
public void basicIncrement(){
basicCount++;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
atomicIncrement();
basicIncrement();
}
}
//主函数
public static void main(String[] args) throws InterruptedException {
AtomicIntegerDemo1 aid = new AtomicIntegerDemo1();
Thread thread1 = new Thread(aid);
Thread thread2 = new Thread(aid);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("原子类的结果:"+atomicInteger.get());
System.out.println("普通变量值:"+basicCount);
}
}
- 给普通类型增加
synchronized
修饰
- 代码演示: getAndAdd()
四、Atomic*Array数组类型原子类
/******
@author 阿昌
@create 2021-06-12 18:17
*******
* 演示原子数组的使用方法
*/
public class AtomicArray {
public static void main(String[] args) {
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
Incrementer incrementer = new Incrementer(atomicIntegerArray);
Decrementer decrementer = new Decrementer(atomicIntegerArray);
Thread[] threadsIncrementer = new Thread[100];
Thread[] threadsDecrementer = new Thread[100];
for (int i = 0; i < 100; i++) {
threadsDecrementer[i] = new Thread(decrementer);
threadsIncrementer[i] = new Thread(incrementer);
threadsDecrementer[i].start();
threadsIncrementer[i].start();
}
// Thread.sleep(10000);
for (int i = 0; i < 100; i++) {
try {
threadsDecrementer[i].join();
threadsIncrementer[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i <atomicIntegerArray.length() ; i++) {
if (atomicIntegerArray.get(i)!=0){
System.out.println("发现了错误: " +i);
}
}
System.out.println("运行结束");
}
}
//自减任务类
class Decrementer implements Runnable{
private AtomicIntegerArray array;
public Decrementer(AtomicIntegerArray array) {
this.array = array;
}
@Override
public void run() {
for (int i = 0; i < array.length(); i++) {
array.getAndDecrement(i);
}
}
}
//自增任务类
class Incrementer implements Runnable{
private AtomicIntegerArray array;
public Incrementer(AtomicIntegerArray array) {
this.array = array;
}
@Override
public void run() {
for (int i = 0; i < array.length(); i++) {
array.getAndIncrement(i);
}
}
}
五、Atomic*Reference引用类型原子类
- 源码,
compareAndSet()
- 这里是之前的自旋锁演示例子
/******
@author 阿昌
@create 2021-06-11 21:10
*******
* 自旋锁演示
*/
public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();
//加锁操作
public void lock(){
Thread current = Thread.currentThread();
//期待是null,如果是期望的,就将其设置为current
while (!sign.compareAndSet(null,current)){
System.out.println(Thread.currentThread().getName()+":自旋获取失败,再次尝试");
}
}
//解锁操作
public void unlock(){
Thread current = Thread.currentThread();
//期待加锁的当前线程,如果是期望的,就将其设置为为null,也就是没有持有了,就是解锁了
sign.compareAndSet(current,null);
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":开始尝试获取自旋锁");
spinLock.lock();
System.out.println(Thread.currentThread().getName() + ":获取到了自旋锁");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLock.unlock();
System.out.println(Thread.currentThread().getName() + ":释放了自旋锁");
}
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
六、AtomicIntegerFieldUpdater升级原子操作
-
AtomicIntegerFieldUpdater
对普通变量进行升级
-
使用场景
- 偶尔需要一个原子get/set操作(如晚上某个时刻他存在大量并发修改,其他时刻就正常)
- 这个变量我们无法操作,只能对他进行升级
-
代码演示
/******
@author 阿昌
@create 2021-06-12 19:02
*******
* 演示AtomicIntegerFieildUpdater的用法
*/
public class AtomicIntegerFieildUpdater implements Runnable {
static Candidate tom;
static Candidate jack;
//newUpdater():参数1指定哪个类,参数2哪个属性
public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
tom.score++;//普通自增
scoreUpdater.getAndIncrement(jack);//通过包装自增
}
}
//候选人类
public static class Candidate{
//分数
volatile int score;
}
//主函数
public static void main(String[] args) throws InterruptedException {
tom = new Candidate();
jack = new Candidate();
AtomicIntegerFieildUpdater a = new AtomicIntegerFieildUpdater();
Thread thread1 = new Thread(a);
Thread thread2 = new Thread(a);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("普通自增: "+tom.score);
System.out.println("升级自增: "+jack.score);
}
}
升级后的操作,都会直接作用到原来对象的属性上:所以直接 jack.score就可
他让我们传入类,和对应属性名,这里就可以感觉到他使用的底层原理是反射
- 注意点
- 不支持
被static修饰
的变量 - 可见范围,
由public修饰的变量
,private不行
- 不支持
七、Adder累加器
-
Java8引入
-
高并发下LongAdder比AtomicLong
效率高
,本质还是空间换时间
-
竞争激烈的情况下,LongAdder会把不同线程对应到不同的Cell上进行修改,降低冲突的概率,是
多段锁
的理念,提高了并发性
1、对比AddderLong & AtomicLong的高并发性能
-
AtomicLong,20个线程并发,每个线程执行10000次
public class AtomicLongDemo { //主函数 public static void main(String[] args) throws InterruptedException { AtomicLong counter = new AtomicLong(0); //新建线程池 ExecutorService pool = Executors.newFixedThreadPool(20); long startTime = System.currentTimeMillis(); //任务次数 for (int i = 0; i < 10000; i++) { pool.submit(new Task(counter)); } //关闭线程池 pool.shutdown(); while (!pool.isTerminated()){ } long endTime = System.currentTimeMillis(); System.out.println(counter.get()); System.out.println("AtomicLong完成时间:"+(endTime-startTime)+"毫秒"); } //任务内部类 public static class Task implements Runnable{ private AtomicLong count; public Task(AtomicLong count) { this.count = count; } @Override public void run() { for (int i = 0; i < 10000; i++) { count.incrementAndGet();//自增 } } } }
花费:
1.959s
-
LongAdder,20个线程并发,每个线程执行10000次
public class LongAdderDemo { //主函数 public static void main(String[] args) throws InterruptedException { LongAdder counter = new LongAdder(); //新建线程池 ExecutorService pool = Executors.newFixedThreadPool(20); long startTime = System.currentTimeMillis(); //任务次数 for (int i = 0; i < 10000; i++) { pool.submit(new Task(counter)); } //关闭线程池 pool.shutdown(); while (!pool.isTerminated()){ } long endTime = System.currentTimeMillis(); System.out.println(counter.sum()); System.out.println("LongAdder完成时间:"+(endTime-startTime)+"毫秒"); } //任务内部类 public static class Task implements Runnable{ private LongAdder count; public Task(LongAdder count) { this.count = count; } @Override public void run() { for (int i = 0; i < 10000; i++) { count.increment();//自增 } } } }
花费:
0.373s
总结:在多线程的情况下,LongAdder比AtomicLong的性能更好
2、为什么AdderLong高并发性能好的原因
- AtomicLong,
每次加法,都需要flush和refresh
,导致消耗资源更多。
- LongAdder,每个线程他自己有
独立的计数器
那这里我就觉得就会出现最后线程不安全的情况,无法保持一致性,那这里就要讲一下他最后的Sum汇总阶段
3、Sum源码分析
他最后会判断,如果有as变量也就是cell[]数组,他就跟base一起相加结果返回最后的值
上面的源码看出,这个遍历相加的内部没有保证线程安全,也就是说如果之前加好的数组元素发生了变动,他就不会实时最新的反应在最终返回的sum总和中,也就是说
返回的sum结果可能不是最新的值
4、AtomicLong & LongAdder对比
- LongAdder
- 消耗更多的空间;
- 在高并发的情况下性能更好;
- 适用于统计求和计数场景;
- 类方法相对较少
八、Accumulator累加器
-
类似与LongAdder,功能更强劲
-
代码演示:基本用法
public class LongAccumulatorDemo {
public static void main(String[] args) {
//参数1:表达式
//参数2:初始值,对X的第一次定义
//最开始会将初始值赋给X ,y就是之前的结果;类似于 数学归纳法
LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 100);
accumulator.accumulate(1);//此时,x=1,y=100,结果为101
accumulator.accumulate(2);//此时,x=2,y=101,结果为103
System.out.println(accumulator.getThenReset());
}
}
<
以上是关于Day296.原子类 -Juc的主要内容,如果未能解决你的问题,请参考以下文章
java并发 day04CAS 原子整数 原子引用 原子数组 字段更新器和原子累加器 unsafe CPU缓存结构 不可变类 final的原理
java并发 day04CAS 原子整数 原子引用 原子数组 字段更新器和原子累加器 unsafe CPU缓存结构 不可变类 final的原理
Day823.Java原子性问题解决方案 -Java 并发编程实战
Day823.Java原子性问题解决方案 -Java 并发编程实战