Java并发编程包中atomic的实现原理示例详解

时间:2022-10-08 17:24:29

线程安全:

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

线程安全主要体现在以下三个方面:

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

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

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

引子

在多线程的场景中,我们需要保证数据安全,就会考虑同步的方案,通常会使用synchronized或者lock来处理,使用了synchronized意味着内核态的一次切换。这是一个很重的操作。

有没有一种方式,可以比较便利的实现一些简单的数据同步,比如计数器等等。concurrent包下的atomic提供我们这么一种轻量级的数据同步的选择。

使用例子

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.concurrent.countdownlatch;
import java.util.concurrent.atomic.atomicinteger;
 
public class app {
 
 public static void main(string[] args) throws exception {
  countdownlatch countdownlatch = new countdownlatch(100);
 
  atomicinteger atomicinteger = new atomicinteger(0);
  for (int i = 0; i < 100; i++) {
   new thread() {
    @override
    public void run() {
     atomicinteger.getandincrement();
 
     countdownlatch.countdown();
    }
   }.start();
  }
 
  countdownlatch.await();
 
  system.out.println(atomicinteger.get());
 }
}

在以上代码中,使用atomicinteger声明了一个全局变量,并且在多线程中进行自增,代码中并没有进行显示的加锁。

以上代码的输出结果,永远都是100。如果将atomicinteger换成integer,打印结果基本都是小于100。

也就说明atomicinteger声明的变量,在多线程场景中的自增操作是可以保证线程安全的。接下来我们分析下其原理。

原理

我们可以看一下atomicinteger的代码

Java并发编程包中atomic的实现原理示例详解

他的值是存在一个volatile的int里面。volatile只能保证这个变量的可见性。不能保证他的原子性。

可以看看getandincrement这个类似i++的函数,可以发现,是调用了unsafe中的getandaddint。

Java并发编程包中atomic的实现原理示例详解

unsafe是何方神圣?unsafe提供了java可以直接操作底层的能力。

进一步,我们可以发现实现方式:

Java并发编程包中atomic的实现原理示例详解

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

优缺点

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

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

java 8做出的改进和努力

在java 8中引入了4个新的计数器类型,longadder、longaccumulator、doubleadder、doubleaccumulator。他们都是继承于striped64。

在longadder 与atomiclong有什么区别?

atomic*遇到的问题是,只能运用于低并发场景。因此longaddr在这基础上引入了分段锁的概念。可以参考《jdk8系列之longadder解析》一起看看做了什么。

大概就是当竞争不激烈的时候,所有线程都是通过cas对同一个变量(base)进行修改,当竞争激烈的时候,会将根据当前线程哈希到对于cell上进行修改(多段锁)。

Java并发编程包中atomic的实现原理示例详解

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:http://zhuanlan.51cto.com/art/201809/583225.htm