java高并发4.1 原子性-Atomic包

线程安全?

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

线程安全性?

线程安全性主要体现在三个方面:原子性、可见性、有序性

 

  • 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作

  • 可见性:一个线程对主内存的修改可以及时的被其他线程观察到

  • 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。

 

java高并发4.1 原子性-Atomic包_第1张图片

____________________________________________________________________________

AtomicInteger

 

我们稍稍对之前第三节的程序做修改(之前的是线程不安全的)

java高并发4.1 原子性-Atomic包_第2张图片

做了这个调整之后

输出结果:

count:5000   线程安全了

 

上边的示例代码就是通过AtomicInteger类保证了线程的原子性。

那么它是如何保证原子性的呢?我们接下来分析一下它的源码。

示例中,对count变量的+1操作,采用的是incrementAndGet方法,此方法的源码中调用了一个名为unsafe.getAndAddInt的方法

getAndAddInt方法的具体实现为:

java高并发4.1 原子性-Atomic包_第3张图片

实质上是用到了CAS

      在此方法中,方法参数为要操作的对象Object var1、期望底层当前的数值为var2、要修改的数值var4。定义的var5为真正从底层取出来的值。采用do..while循环的方式去获取底层(主内存)数值并与期望值进行比较,比较成功才将值进行修改。而这个比较再进行修改的方法就是compareAndSwapInt就是我们所说的CAS*,它是一系列的接口,比如下面罗列的几个接口。使用native修饰,是底层的方法。CAS取的是compareAndSwap三个单词的首字母.

  • 底层(主内存

  • count里存的值为工作内存

另外,示例代码中的count可以理解为JMM中的工作内存,而这里的底层数值即为主内存,如果看过我上一篇文章的盆友就能把这一块的知识点串联起来了。

 

实现原理:

  如何保证原子性:自旋 + CAS(乐观锁)。在这个过程中,通过compareAndSwapInt比较更新value值,如果更新失败,重新获取旧值,然后更新。

优缺点

    CAS相对于其他锁,不会进行内核态操作,有着一些性能的提升。但同时引入自旋,当锁竞争较大的时候,自旋次数会增多。cpu资源会消耗很高。

    换句话说,CAS+自旋适合使用在低并发有同步数据的应用场景。

 

可以看到大概实现原理是:通过CAS乐观锁保证原子性,通过自旋保证当次修改的最终修改成功,通过降低锁粒度(多段锁)增加并发性能。**

____________________________________________________________________________

AtomicLong 与 LongAdder

LongAdder是java8为我们提供的新的类,跟AtomicLong有相同的效果。首先看一下代码实现:

AtomicLong:

//变量声明

public static AtomicLong count = new AtomicLong(0);

//变量操作

count.incrementAndGet();

//变量取值

count.get();

其他都和之前的代码一样

LongAdder:

//变量声明

public static LongAdder count = new LongAdder();

//变量操作

count.increment();

//变量取值

count

那么问题来了,为什么有了AtomicLong还要新增一个LongAdder呢?

原因是:CAS底层实现是在一个死循环中不断地尝试修改目标值,直到修改成功。如果竞争不激烈的时候,修改成功率很高,否则失败率很高。在失败的时候,这些重复的原子性操作会耗费性能。

知识点: 对于普通类型的long、double变量,JVM允许将64位的读操作或写操作拆成两个32位的操作。

    LongAdder类的实现核心是将热点数据分离,比如说它可以将AtomicLong内部的内部核心数据value分离成一个数组,每个线程访问时,通过hash等算法映射到其中一个数字进行计数,而最终的计数结果则为这个数组的求和累加,其中热点数据value会被分离成多个单元的cell,每个cell独自维护内部的值。当前对象的实际值由所有的cell累计合成,这样热点就进行了有效地分离,并提高了并行度。这相当于将AtomicLong的单点的更新压力分担到各个节点上。在低并发的时候通过对base的直接更新,可以保障和AtomicLong的性能基本一致。而在高并发的时候通过分散提高了性能。

    缺点:如果在统计的时候,如果有并发更新,可能会有统计数据有误差。实际使用中在处理高并发计数的时候优先使用LongAdder,而不是AtomicLong在线程竞争很低的时候,使用AtomicLong会简单效率更高一些。比如序列号生成 (准确性)

LongAdder中采用了类似的分段CAS操作,失败则自动迁移到下一个分段进行CAS的思路。**

____________________________________________________________________________

AtomicReference

java高并发4.1 原子性-Atomic包_第4张图片

最后结果:

count = 4

____________________________________________________________________________

AtomicIntegerFieldUpdater

这个类的核心作用是用一个原子类更新一个指定的类(如AtomicExample5这个类)的某一个字段(count)的。并且这个字段一定要用volatile修饰同时还不能是static的。

 

 java高并发4.1 原子性-Atomic包_第5张图片

 

输出结果:

____________________________________________________________________________

AtomicStampReference与CAS的ABA问题

什么是ABA问题?

CAS操作的时候,其他线程将变量的值A改成了B,但是随后又改成了A,本线程在CAS方法中使用期望值A与当前变量进行比较的时候,发现变量的值未发生改变,于是CAS就将变量的值进行了交换操作。但是实际上变量的值已经被其他的变量改变过,这与设计思想是不符合的。所以就有了AtomicStampReference。

如何解决ABA问题?

AtomicStampReference的处理思想是,每次变量更新的时候,将变量的版本号+1,之前的ABA问题中,变量经过两次操作以后,变量的版本号就会由1变成3,也就是说只要线程对变量进行过操作,变量的版本号就会发生更改。从而解决了ABA问题。

____________________________________________________________________________

AtomicLongArray

这个类实际上维护了一个Array数组,我们在对数值进行更新的时候,会多一个索引值让我们更新。

    原子性,提供了互斥访问,同一时刻只能有一个线程来对它进行操作。那么在java里,保证同一时刻只有一个线程对它进行操作的,

除了Atomic包之外,还有锁的机制**。JDK提供锁主要分为两种:synchronized和Lock。接下来我们了解一下synchronized。

____________________________________________________________________________

AtomicBoolean

这个类中值得一提的是它包含了一个名为compareAndSet的方法,这个方法可以做到的是控制一个boolean变量在一件事情执行之前为false,事情执行之后变为true。或者也可以理解为可以控制某一件事只让一个线程执行,并仅能执行一次。*

他的源码如下

 

java高并发4.1 原子性-Atomic包_第6张图片

 

结果: 

isHappened : true

for 循环5000次但log只执行一次

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(高并发,并发编程)