线程安全性详解(原子性、可见性、有序性)(1)

  • AtomicReference、AtomicReferenceFieldUpdater

  • AtomicStampReference:CAS的ABA问题

由于CAS原语的直接操作与计算机底层的联系很大,CAS原语有三个参数,内存地址期望值新值。我们在Java中一般不去直接写CAS相关的代码,JDK为我们封装在AtomicXXX中,因此,我们直接使用就可以了。

我们在 java.util.concurrent.atomic目录中可以看到我们这些类,包下提供了AtomicBooleanAtomicLongAtomicInteger三种原子更新基本类型和一个比较好玩的类AtomicReference,这些类都有一个共同点,都支持CAS,以AtomicInteger为重点讲解。

在这里插入图片描述

3.1.1、AtomicInteger

AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减

以下是AtomicIntege基本包括的方法:

public final int getAndSet(int newValue) //给AtomicInteger设置newValue并返回加oldValue

public final boolean compareAndSet(int expect, int update) //如果输入的值和期望值相等就set并返回true/false

public final int getAndIncrement() //对AtomicInteger原子的加1并返回当前自增前的value

public final int getAndDecrement() //对AtomicInteger原子的减1并返回自减之前的的value

public final int getAndAdd(int delta) //对AtomicInteger原子的加上delta值并返加之前的value

public final int incrementAndGet() //对AtomicInteger原子的加1并返回加1后的值

public final int decrementAndGet() //对AtomicInteger原子的减1并返回减1后的值

public final int addAndGet(int delta) //给AtomicInteger原子的加上指定的delta值并返回加后的值

示例:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j

public class AtomicIntegerExample {

// 请求总数

public static int clientTotal = 5000;

// 同时并发执行的线程数

public static int threadTotal = 200;

public static AtomicInteger count = new AtomicInteger(0);

public static void main(String[] args) throws Exception {

//获取线程池

ExecutorService executorService = Executors.newCachedThreadPool();

//定义信号量

final Semaphore semaphore = new Semaphore(threadTotal);

final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);

for (int i = 0; i < clientTotal ; i++) {

executorService.execute(() -> {

try {

semaphore.acquire();

add();

semaphore.release();

} catch (Exception e) {

log.error(“exception”, e);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

log.info(“count:{}”, count.get());

}

private static void add() {

count.incrementAndGet();

}

}

这里我们使用请求总数为:5000,同时执行的并发线程数为:200,我们最终需要得到结果为:5000,这个执行结果才算正确。

查看返回结果:

13:43:26.473 [main] INFO com.mmall.concurrency.example.atomic.AtomicIntegerExample - count:5000

最后结果是 5000表示是线程安全的。

我们来看看 AtomicInteger底层代码中到底为我们做了什么?首先我们来看 AtomicInteger.incrementAndGet()方法

public class AtomicInteger extends Number implements java.io.Serializable{

/**

  • 对AtomicInteger原子的加1并返回加1后的值

  • @return 更新的值

*/

public final int incrementAndGet() {

return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

}

}

AtomicInteger调用了java底层的 unsafe.getAndAddInt()方法,这里是实现CAS 的关键。

incrementAndGet()是将自增后的值返回,还有一个方法getAndIncrement()是将自增前的值返回,分别对应++ii++操作。同样的decrementAndGet()getAndDecrement()则对--ii--操作。

Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于

Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作

能力方面起了很大的作用。Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。

过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。通常我们最好也不

要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。

再来看 Unsafe.getAndAddInt()方法

/*

  • 其中getIntVolatile和compareAndSwapInt都是native方法

  • getIntVolatile是获取当前的期望值

  • compareAndSwapInt就是我们平时说的CAS(compare and swap),通过比较如果内存区的值没有改变,那么就用新值直接给该内存区赋值

*/

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;

}

public native int getIntVolatile(Object var1, long var2);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

我们可以看到getAndAddInt(Object var1, long var2, int var4),传进来的第一个参数是当前的一个对象,也就是我们的:count.incrementAndGet(),在getAndAddInt()中,var1就是count,var2就是当前的值,比如当前循环中count值为 2,var4为每次递增1

其次getAndAddInt()方法中涉及到的两个方法调用都定义为native,即java底层实现的本地方法

  • getIntVolatile():获取保存当前对象count的主存地址的引用(注意不是对象的值,是引用)。

  • compareAndSwapInt():比较当前对象的值和底层该对象的值是否相等,如果相等,则将当前对象值加1,如果不相等,则重新去获取底层该对象的值,这个方法的实现就是CPU的CAS(compare and swap)操作。

我们知道volatile具有一致性的特征,但是它不具备原子性,为什么AtomicInteger却同时具备一致性和原子性,原来在AtomicInteger源码中实现了这样一串代码:private volatile int value;,在AtomicInteger内部实现就使用了volatile关键字,这就是为什么执行CAS(对CAS有疑问的点击此处)操作的时候,从底层获取的数据就是最新的数据:

如果当前要保存的值和内存中最新的值不相等的话,说明在这个过程中被其他线程修改了,只

能获取更新当前值为最新值,再那这个当前值再去和重新去内存获取的最新值比较,直到二者

相等的时候,才完成+1的过程.

使用AtomicInteger的好处在于,它不同于sychronized关键字或lock用锁的形式来实现原子性,加锁会影响性能,而是采用循环比较的形式来提高性能。

3.1.2、AtomicLong

AtomicLong是作用是对长整形进行原子操作,依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

import java.util.concurrent.atomic.AtomicLong;

@Slf4j

public class AtomicLongExample {

// 请求总数

public static int clientTotal = 5000;

// 同时并发执行的线程数

public static int threadTotal = 200;

public static AtomicLong count = new AtomicLong(0);

public static void main(String[] args) throws Exception {

ExecutorService executorService = Executors.newCachedThreadPool();

final Semaphore semaphore = new Semaphore(threadTotal);

final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);

for (int i = 0; i < clientTotal ; i++) {

executorService.execute(() -> {

try {

semaphore.acquire();

add();

semaphore.release();

} catch (Exception e) {

log.error(“exception”, e);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

log.info(“count:{}”, count.get());

}

private static void add() {

count.incrementAndGet();

// count.getAndIncrement();

}

}

执行结果:

14:59:38.978 [main] INFO com.mmall.concurrency.example.atomic.AtomicLongExample - count:5000

最后结果是 5000表示是线程安全的。

3.1.3、AtomicBoolean

AtomicBoolean位于java.util.concurrent.atomic包下,是java提供给的可以保证数据的原子性操作的一个类

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j

public class AtomicBooleanExample {

private static AtomicBoolean isHappened = new AtomicBoolean(false);

// 请求总数

public static int clientTotal = 5000;

// 同时并发执行的线程数

public static int threadTotal = 200;

public static void main(String[] args) throws Exception {

ExecutorService executorService = Executors.newCachedThreadPool();

final Semaphore semaphore = new Semaphore(threadTotal);

final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);

for (int i = 0; i < clientTotal ; i++) {

executorService.execute(() -> {

try {

semaphore.acquire();

test();

semaphore.release();

} catch (Exception e) {

log.error(“exception”, e);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

log.info(“isHappened:{}”, isHappened.get());

}

private static void test() {

if (isHappened.compareAndSet(false, true)) {

log.info(“execute”);

}

}

}

返回结果:

15:04:54.954 [pool-1-thread-2] INFO com.mmall.concurrency.example.atomic.AtomicBooleanExample - execute

15:04:54.971 [main] INFO com.mmall.concurrency.example.atomic.AtomicBooleanExample - isHappened:true

这里我们发现log.info("execute");,在代码中只执行了一次,并且isHappened:true的值为true,这是为啥呢?

这里是因为当程序第一次compareAndSet()的时候,使得isHappend变为了true,因为原子性的关系,没有其他线程进行干扰,通过使用AtomicBoolean,我们使某段代码只执行一次。

3.1.4、AtomicReference

AtomicReferenceAtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。

多个线程之间的操作无论采用何种执行时序或交替方式,都要保证不变性条件不被破坏,要

保持状态的一致性,就需要在单个原子操作中更新相关的状态变量。

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicReference;

@Slf4j

public class AtomicReferenceExample {

private static AtomicReference count = new AtomicReference<>(0);

public static void main(String[] args) {

count.compareAndSet(0, 2);

count.compareAndSet(0, 1);

count.compareAndSet(1, 3);

count.compareAndSet(2, 4);

count.compareAndSet(3, 5);

log.info(“count:{}”, count.get());

}

}

大家觉得我们输出的结果会是多少?

返回结果:

15:26:59.680 [main] INFO com.mmall.concurrency.example.atomic.AtomicReferenceExample - count:4

为什么是4呢?

首先我们 要说的是public final boolean compareAndSet(V expect, V update)这个方法,这个方法主要的作用是通过比对两个对象,然后更新为新的对象,这里的比对两个对象,比对的方式不是equals而是==,意味着比对的是内存的中地址。

1、首先我们创建count的初始化为0

2、在main方法中 count.compareAndSet(0, 2);,判断count为0时赋值为2

3、在count.compareAndSet(0, 1);count.compareAndSet(1, 3);判断count是否为1或者0,因为上一步我们已经赋值为2了,所以判断不成立

4、在count.compareAndSet(2, 4);判断count是否为2,等式成立

5、最好输出结果为4

count.compareAndSet(0, 2); //count=0?赋值 2,判断成立,此时count=0,更新后为2

count.compareAndSet(0, 1); //count=0?赋值 1,判断不成立,此时count=2

count.compareAndSet(1, 3); //count=1?赋值 3,判断不成立,此时count=2

count.compareAndSet(2, 4); //count=2?赋值 4,判断成立,此时count=2,更新后count=4

count.compareAndSet(3, 5); //count=3?赋值 5,判断不成立,此时count=4

所以我们输出结果为:4

3.1.5、CAS中ABA问题的解决

CAS并非完美的,它会导致ABA问题,例如:当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。比如在链表中。

如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号都会变话,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。

在JDK中提供了AtomicStampedReference类来解决这个问题,这个类维护了一个int类型的标记stamp,每次更新数据的时候顺带更新一下stamp。

3.2、原子性 — synchronized

synchronized是一种同步锁,通过锁实现原子操作。

1、修饰代码块:大括号括起来的代码,作用于调用的对象

2、修饰方法:整个方法,作用于调用的对象

3、修饰静态方法:整个静态方法,作用于所有对象

4、修饰类:括号括起来的部分,作用于所有对象

详细可以查看,我写的关于:synchronized的博客,因为写过所以就不做过多描述。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
img

还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!

王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。

对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!

【完整版领取方式在文末!!】

93道网络安全面试题

内容实在太多,不一一截图了

黑客学习资源推荐

最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

1️⃣零基础入门
① 学习路线

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

image

② 路线对应学习视频

同时每个成长路线对应的板块都有配套的视频提供:

image-20231025112050764

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

919614bde78921fb2f8ddf0c2f.png)

内容实在太多,不一一截图了

黑客学习资源推荐

最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

1️⃣零基础入门
① 学习路线

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

image

② 路线对应学习视频

同时每个成长路线对应的板块都有配套的视频提供:

image-20231025112050764

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-v0WtAAu3-1712918161327)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值