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

 更新时间:2018年09月13日 15:41:45   作者:林湾村龙猫  
这篇文章主要给大家介绍了关于Java并发编程包中atomic的实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

线程安全:

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

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

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

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

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

引子

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

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

使用例子

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的代码

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

可以看看getAndIncrement这个类似i++的函数,可以发现,是调用了UnSafe中的getAndAddInt。

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

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

如何保证原子性:自旋 + 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上进行修改(多段锁)。

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

总结

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

相关文章

  • Java实现图书管理系统的示例代码

    Java实现图书管理系统的示例代码

    这篇文章将通过Java实现一个简答的图书管理系统,本图书管理系统用对象数组的方式来提供操作方法,比较特别,建议新手学习,这对理解Java面向对象有很大帮助
    2022-11-11
  • Java多线程教程之如何利用Future实现携带结果的任务

    Java多线程教程之如何利用Future实现携带结果的任务

    Callable与Future两功能是Java 5版本中加入的,这篇文章主要给大家介绍了关于Java多线程教程之如何利用Future实现携带结果任务的相关资料,需要的朋友可以参考下
    2021-12-12
  • Java中的同步与异步详细介绍

    Java中的同步与异步详细介绍

    这篇文章主要介绍了Java中的同步与异步详细介绍,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • 深入理解Java8双冒号::的使用

    深入理解Java8双冒号::的使用

    这篇文章主要介绍了深入理解Java8双冒号::的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • idea中如何创建scala项目

    idea中如何创建scala项目

    idea中创建scala项目有三种方式1.通过maven;2.通过idea;3.通过sbt的方式;本文就每种方法通过图文并茂的形式给大家详细介绍,需要的朋友参考下吧
    2021-07-07
  • javaWeb实现学生信息管理系统

    javaWeb实现学生信息管理系统

    这篇文章主要为大家详细介绍了javaWeb实现学生信息管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • RabbitMQ基础概念之信道channel详解

    RabbitMQ基础概念之信道channel详解

    这篇文章主要介绍了RabbitMQ基础概念之信道channel详解,信道是生产消费者与rabbit通信的渠道,生产者publish或者消费者消费一个队列都是需要通过信道来通信的,需要的朋友可以参考下
    2023-08-08
  • Spring AOP使用之多切面运行顺序

    Spring AOP使用之多切面运行顺序

    这篇文章主要介绍了Spring AOP使用之多切面运行顺序,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java基础篇之List集合举例详解

    Java基础篇之List集合举例详解

    Java中的List集合是一种常用的数据结构,它提供了一种有序、可重复的元素集合,下面这篇文章主要给大家介绍了关于Java基础篇之List集合的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • Java设计模式以虹猫蓝兔的故事讲解装饰器模式

    Java设计模式以虹猫蓝兔的故事讲解装饰器模式

    装饰器模式又名包装(Wrapper)模式。装饰器模式以对客户端透明的方式拓展对象的功能,是继承关系的一种替代方案,本篇文章以虹猫蓝兔生动形象的为你带来详细讲解
    2022-04-04

最新评论