AtomicInteger和volatile

2024-03-01 19:08
文章标签 volatile atomicinteger

本文主要是介绍AtomicInteger和volatile,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

AtomicInteger中的方法线程安全,它拥有一个volatile修饰的int类型的value值,我们通过AtoicInteger对象对value进行操作是线程安全的,以getAndIncrement()方法为例说明它是如何实现的,我们先看下源码

public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);}
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;}

可以看到它使用了unsafe对象的getAndAddInt方法,我们知道java项目是运行在jvm上的,无法直接访问操作系统底层,unsafe对象提供了很多native方法,可以对内存进行直接操作,unsafe对象不能直接获取,在项目中如果相使用的话只能通过反射获取。我们点进去unsafe的getAndAddInt方法看一下。var2就是我们刚才传进来的valueOffset 代表了value值在内存中的地址偏移量,var4是1,var1就是AtomicInteger对象,unsafe中的方法具有原子性,它的执行不会被打断,它会先获取var1对象对应的value在内存中的值var5,然后使用cas方法将value值进行+1,并放入valueoffset对应的内存地址中,这个过程中可能有多个线程使用同一个原value值对其进行cas操作,但是只会有一个线程成功,而失败线程并不会陷入阻塞状态可以进行重试,可以看到它使用了do while,如果cas执行失败就重复执行这一过程,直到成功。

unsafe的compareAndSwapInt具有原子性且,变量由volatile修饰具有可见性,因此实现了线程安全(通过unsafe类内的方法,利用底层硬件提供的原子性指令保证操作的原子性,用的最多的就是cas操作)

由volatile关键字修饰的变量拥有的可见性和部分顺序性

1.可见性

当一个共享变量被volatile修饰时,它会保证修改的值除了存储进自己工作内存中,还会立即被更新到主存,并将其他线程中该变量的引用置为无效,当有其他线程需要使用该变量时,它会先检查该变量是否还有效,如果无效则去内存中读取新值。即一个线程修改了某个volatile变量的值,这新值对其他线程来说是立即可见的

//线程1
boolean stop = false;
while(!stop){doSomething();
}//线程2
stop = true;

如上面的代码,线程1先执行,线程2修改stop为true后,线程1可执行其他操作,一般线程将变量存储进内部缓存后会将其再存入主存,但如果发生了某些特殊情况。线程2将stop变为true放入了内部缓存后并没有立即将其存入主存,此时线程2又恰好挂掉了,那么线程1就陷入了无限等待之中。而如果stop被volatile修饰的话,当线程2修改了stop值后,就会立即把它刷入主存之中,线程1使用stop时会感知到它不可用,就会再从主存中获取它,而不会出现刚才说的情况

2.禁止指令重排序

一条代码语句会被转化为字节码指令执行时,但由于编译器和处理器的存在,会在不改变执行结果的情况下对指令的执行执行顺序进行重排,优化程序的运行效率,但在多线程情况下有可能出现错误。

(1)volatile关键字能够保证,当执行到它修饰的变量读写操作时(假设 volatile修饰变量a,执行 a=1),a=1前面的操作肯定已经完成,且可以对a=1其后的操作可见

(2)并且a=1之前的操作不能在它之后执行,a=1之后的操作也不能在它之前执行

说的有点绕,看个例子

x = 2;        //语句1
y = 0;        //语句2
volatile flag = true;  //语句3
x = 4;         //语句4
y = -1;       //语句5

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。因为flag由volatile修饰,所以,1、2语句顺序可以交换但它肯定在3之前执行完毕,并且对4、5可见,4、5的执行顺序可以交换,但是必须在3之后执行。

并且重排序时有关联的语句相对顺序是不会变的,也就是说假如flag没有被volatile修饰,1肯定出现在4前面。2肯定在5前面,而3号语句可以在任意位置。这样才可以保证,单线程情况下,虽然重排序,但是最终结果不会发生改变。大家看到这里,可能觉得我刚才说的“指令重排序可能会导致多线程情况下出错”这句话说的不对,看下这个例子

//线程1
context = initialize()//初始化 语句1
isComplete = true  //语句2//线程2
while(!isComplete){sleep(1s)
}
context.doSomthing()

线程1执行语句1和2,它们可能发生重排序,在单线程情况下,并没有什么影响,但是在多线程情况下,如果语句2在1之前执行,虽然它变成了true但是实际的初始化并没有完成,线程2使用context执行操作时就会出错。假如使用volatile修饰isComplete后,会保证初始化操作在isComplete=true之前完成,这样就可以确保线程2不会出错。

实现原理:

当变量被volatile修饰后,进行编译后会多出一个lock指令,引入了内存屏障,有3方面作用:

(1)当该变量发生改变后,会将当前线程工作内存中的数据立刻回写到系统主内存;

(2)使在其他线程里缓存了该数据的内存地址无效

(3)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成

volatile可以用在懒汉双检查单例模式之中

public class Singleton {private static volatile Singleton mySingleton = null;private Singleton(){}public static Singleton getSingleton() {if(mySingleton == null) {synchronized(Singleton.class) {if(mySingleton == null) {mySingleton = new Singleton();}}}return mySingleton;}
}

这里为什么使用volatile修饰mySingleton呢,主要是使用了其禁止指令重排序的功能。其实 mySingleton = new Singleton()的过程可以分为三步

(1)在堆中开辟对象存储空间

(2)在该存储空间实例化对象、初始化

(3)mySingleton指向为Singleton分配的内存空间

因为1、3都与内存空间相关,所以它们的相对顺序是一定的,所以重排序后可能为1-2-3,或1-3-2,后面这种情况时,当3执行完毕后mySingleton就已经不指向null了,这时候如果又有其他线程来获取单例对象,那么就获取了该引用,但是Singleton对象还没有进行实例化,如果调用了其中的方法,很有可能的就报错了。现在在mySingleton上加了volatile,那么就可以保证在执行3时1和2肯定已经完成,那么就不会出错了。

下面给一下饿汉模式

public class Singleton {private static Singleton mySingleton = new Singleton();private Singleton(){}public static Singleton getMySingleton(){return mySingleton;}
}
//饿汉模式初始化就创建了对象, 每次调用都返回同一个对象。//饿汉模式是线程安全的。

volatile与synchronized的区别

1.volatile不会导致线程阻塞,synchronized会导致线程阻塞
2.volatile修饰的变量操作具有可见性但是没有原子性,而synchronnized修饰的方法或代码块中的变量操作即有可见性,又具有原子性,同时又保障多线程访问的顺序性,synchronized的可见性是通过monitorEnter和monitorExit实现的,当 线程获取synchronized中monitor使用权的时候,会执行montorEnter方法,它会使线程重新从主存获取同步方法/代码块 中变量的值,当执行完毕执行montorExit释放锁时,又会将变量值刷入主存之中。synchronized原子性体现在执行synchronized修饰代码段的线程是不可终止的
3.volatile仅可以修饰变量,但是synchronized可以修饰对象、方法
4.volatile标记的变量不会被指令重排序,而synchronized修饰的变量会被编译器优化

这篇关于AtomicInteger和volatile的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/763507

相关文章

关键字synchronized、volatile的比较

关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字的执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。多线程访问volatile不会发生阻塞,而synchronize

关键字volatile有什么含意?

1. 并行设备的硬件寄存器。存储器映射的硬件寄存器通常加volatile,因为寄存器随时可以被外设硬件修改。当声明指向设备寄存器的指针时一定要用volatile,它会告诉编译器不要对存储在这个地 址的数据进行假设,也就是要去相应的内存地址里取。 2. 一个中断服务程序中修改的供其他程序检测的变量。volatile提醒编译器,它后面所定义的变量随时都有可能改变。因此编译后的程序每次需要存储或读取这

java内存模型及volatile关键字

文章目录 一、基本概念二、java内存模型三、volatile关键字 一、基本概念 在并发编程过程中,我们经常会遇到三类问题:原子性问题,可见性问题,有序性问题。下面我们来介绍一下和这些问题相关的三个概念。 1.原子性 也就是执行一个操作,要不全部执行成功,要不执行失败。比如a=0,这个操作就是原子性的,要么赋值成功,要么赋值失败。再比如a++操作,这个操作就不是原子性的,它是三

Java并发编程--深入理解volatile关键字

前言 一个月以前就准备写篇关于volatile关键字的博客,一直没有动笔,期间看了大量的文章,发现一个小小volatile关键字竟然涉及JMM(Java memory model),JVM(Java virtual machine),Java多线程同步与安全各个方面的知识,写起了非常的困难,后面附带的参考文献仅仅是我看过文献的一部分。 Java memory model(Java内存模型)

java volatile变量及其使用场景

java中的一种稍弱的同步机制,就是volatile变量,用于确保将变量的更新操作通知到其他线程。 变量声明为volatile后: (1)编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序(重排序不懂的,可以自行百度,需要理解)。 (2)volatile变量不会被缓存在寄存器或对其他处理器不可见的地方 因此volatile变量总是会返回最新的值。

【编程底层思考】Java中volatile关键字的使用和作用

在编程中,volatile 关键字是一个类型限定符,它用来告诉编译器,即使这个变量被频繁地访问,也不应该优化掉对它的读写操作。volatile 通常用于多线程编程中,特别是在嵌入式系统或硬件接口编程中,用来确保对某些变量的访问是按照代码的顺序执行的,而不是被编译器重排序。 以下是 volatile 关键字的一些使用场景和作用: 防止编译器优化:编译器在编译代码时,可能会对代码进行优化,以提高程序

J.U.C Review - volatile / synchronized / 锁 深入剖析

文章目录 几个基本概念内存可见性重排序happens-before规则 volatile的内存语义内存可见性禁止重排序内存屏障 volatile的用途总结 synchronized与锁Synchronized关键字Java对象头无锁、偏向锁、轻量级锁和重量级锁偏向锁实现原理撤销偏向锁 轻量级锁重量级锁 锁的升级流程各种锁的优缺点对比 几个基本概念 内存可见性 在Jav

Redis的内存淘汰策略- volatile-lru

`volatile-lru` 策略简介 在 `volatile-lru` 策略下,当 Redis 的内存使用达到配置的上限(`maxmemory`)时,它会优先删除那些设置了过期时间的键,并且选择最近最少使用的键进行删除。LRU 算法的核心思想是,优先删除那些最近没有被访问的数据,以腾出内存空间给新数据或更常用的数据。 这种策略适用于以下场景: - 需要优先删除临时数据的场景。 - 应用中

线程安全关键字synchronized和volatile

Java内存模型(JMM) 提到这两个有关于线程的关键字,那么我们不得不提到Java的内存模型了(JMM),下面我们先看一下Java内存模型在处理多线程方面的工作原理图。 Java内存模型(java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。 工作内存是线程私有的,各个线程之间不共享

多线程——volatile和static

如果变量用 public static修饰,那么这个变量会被放在JVM的方法区。如果是在多线程的环境下,那么这个变量应该是多个线程均可见的。在JVM被设置为-server模式时,JVM为了提升线程运行的效率,一直在私有堆栈中取值,这会导致不同步。解决办法就是使用volatile关键字修饰变量,强制JVM从公共内存取值。