当前位置:首页 > 职业发展 > 正文

关于JAVA 中volatile使用的一些笔记

  • 2025-04-06
  • 172
写在前面我的需求:

问答看到一个小伙伴问了这样JAVA并发的问题,然后我做了解答,主要使用了volatile

我需要解决的问题:我是这样做的:

微信群里问了大佬。使用了原子类(atomic)解决这个问题。

这里对volatile总结一下,当然没有涉及啥底层的东西,很浅。

太敏感的人会体谅到他人的痛苦,自然就无法轻易做到坦率。所谓的坦率,其实就是暴力。-----太宰治《候鸟》

下面是我最开始的解决方案,使用volatile来处理线程安全问题,认为我每次都可以拿到最新的,即可以满足线程安全。但是打印出来的数据有重复的,忽略了volatile修饰变量不满足原子性的问题,而index++本身也不是原子操作,所以会有重票的问题

;;;;/***@ClassnameTicket*@DescriptionTODO*@Date2021/12/819:00*@CreatedLiRuilong*/publicclassTicketimplementsRunnable{//最多受理100笔业务privatestaticfinalintMAX=100;//开始业务//staticAtomicIntegerindex=newAtomicInteger(0);privatestaticvolatileintindex=0;//电影院3秒,时光网5秒,美团2秒,支付宝6秒;privatestaticvolatileMapString,ArrayListmap=newHashMap(){{put("电影院",newArrayList());put("时光网",newArrayList());put("美团",newArrayList());put("支付宝",newArrayList());}};@Overridepublicvoidrun(){while(indexMAX){try{StringcurrentThreadName=().getName();switch(currentThreadName){case"电影院":{("电影院").add(++index);(3);}break;case"时光网":{("时光网").add(++index);(5);}break;case"美团":{("美团").add(++index);(2);}break;case"支付宝":{("支付宝").add(++index);(6);}break;default:break;}}catch(Exceptione){();}}}publicstaticvoidmain(String[]args){finalTickettask=newTicket();//电影院3秒,时光网5秒,美团2秒,支付宝6秒;newThread(task,"电影院").start();newThread(task,"时光网").start();newThread(task,"美团").start();newThread(task,"支付宝").start();().addShutdownHook(newThread(){@Overridepublicvoidrun(){((o1,o2)-{(o1+"|总票数:"+()+().reduce(":",(a,b)-a+""+b));});}});}}==========================支付宝|总票数:18:2877828794100电影院|总票数:32:3471012161923252732353740434649525658626568737680838689929598时光网|总票数:20:8343844485459647美团|总票数:43:2469111262934547505666770727478798284868890939699

后来通过使用原子类,使用了AtomicInteger来满足index++的原子性,票数可以正常的打印出来。

;;;;;;/***@ClassnameTicket*@DescriptionTODO*@Date2021/12/819:00*@CreatedLiRuilong*/publicclassTicketimplementsRunnable{//最多受理100笔业务privatestaticfinalintMAX=100;//开始业务staticAtomicIntegerindex=newAtomicInteger(0);//privatestaticvolatileintindex=0;//电影院3秒,时光网5秒,美团2秒,支付宝6秒;privatestaticvolatileMapString,ArrayListmap=newHashMap(){{put("电影院",newArrayList());put("时光网",newArrayList());put("美团",newArrayList());put("支付宝",newArrayList());}};@Overridepublicvoidrun(){while(()MAX){try{StringcurrentThreadName=().getName();switch(currentThreadName){case"电影院":{("电影院").add((1));(3);}break;case"时光网":{("时光网").add((1));(5);}break;case"美团":{("美团").add((1));(2);}break;case"支付宝":{("支付宝").add((1));(6);}break;default:break;}}catch(Exceptione){();}}}publicstaticvoidmain(String[]args){finalTickettask=newTicket();//电影院3秒,时光网5秒,美团2秒,支付宝6秒;newThread(task,"电影院").start();newThread(task,"时光网").start();newThread(task,"美团").start();newThread(task,"支付宝").start();().addShutdownHook(newThread(){@Overridepublicvoidrun(){((o1,o2)-{(o1+"|总票数:"+()+().reduce(":",(a,b)-a+""+b));});}});}}=================================支付宝|总票数:16:41016222936480879399电影院|总票数:27:26923439424649525759646779297时光网|总票数:17:384485460657278848996美团|总票数:40:2023252834550566697074767989598100
关于volatile的使用

被volatile关键字修饰的实例变量或者类变量具备两层语义:

保证了不同线程之间对共享变量的可见性,

禁止对volatile变量进行重排序。

每个线程都运行在栈内存中,每个线程都有自己的工作内存(WorkingMemory),比如寄存器Register,高速缓存存储器Cache等,线程的计算一般是通过工作内存进行交互的,线程在初始化时从主内存中加载所需要的变量值到工作内存中,然后在线程运行时,如果读取内存,则直接从工作内存中读取,若是写入则先写入到工作内存中,之后在刷新到主内存中。

在多线程情况下,可能读到的不是最新的值,可以使用synchronized同步代码块,或使用Lock锁来解决该问题。JAVA可以使用volatile解决,在变量前加volatile关键字,可以保证每个线程对本地变量的访问和修改都是直接与主内存交互的,而不是与本线程的工作内存交互。

但是Volatile关键字并不能保证线程安全,换句话讲它只能保证当前线程需要该变量的值能够获得最新的值,而不能保证多个线程修改的安全性。

使用volatile,需要保证:

对变量的写操作不依赖于当前值;

该变量没有包含在具有其他变量的不变式中

关于volatile的一些基本概念

volatile关键字只能修饰类变量和实例变量,对于方法参数,局部变量已及实例常量,类常量都不能进行修饰。

原子性,有序性和可见性

并发编程的三个重要的特性

可见性

有序性

原子性

当一个线程对共享变量进行了修改,那么另一个变量可以立即看到。volatile具有保证可见性的语义

Java在运行期会对代码进行优化,执行顺序未必就是编译顺序,volatile具有保证有序性的语义。

多个原子性的操作在一起就不再是原子性操作了。

Java提供了以下三种方式来保证可见性

Java提供了三种保证有序性的方式,具体如下

简单的读取与赋值操作是原子性的,将一个变量赋给另外一个变量的操作不是原子性的。

使用关键字volatile,当一个变量被volatile关键字修饰时,对于共享资源的读操作会直接在主内存中进行(当然也会缓存到工作内存中,当其他线程对该共享资源进行了修改,则会导致当前线程在工作内存中的共享资源失效,所以必须从主内存中再次获取),对于共享资源的写操作当然是先要修改工作内存,但是修改结束后会立刻将其刷新到主内存中。
通synchronized关键字实现,同步块保证任何时候只有一个线程获得锁,然后执行同步方法,并且还会确保在锁释放之前,会将对变量的修改刷新到主内存当中
通过JUC提供的显式锁Lock也能够保证可见性,Lock的lock方法能够保证在同一时刻只有一个线程获得锁然后执行同步方法,并且会确保在锁释放(Lock的unlock方法)之前会将对变量的修改刷新到主内存当中。

使用volatile关键字来保证有序性。使用synchronized关键字来保证有序性。使用显式锁Lock来保证有序性。

Java内存模型(JMM)只保证了基本读取和赋值的原子性操作,其他的均不保证,如果想要使得某些代码片段具备原子性,需要使用关键字synchronized,或者JUC中的lock。如果想要使得int等类型自增操作具备原子性,可以使用JUC包下的原子封装类型*

volatile和synchronized区别

区别

描述

使用上区别

volatile关键字只能用来修饰实例变量或者类变量,不能修饰方法已及方法参数和局部变量和常量。
synchronized关键字不能用来修饰变量,只能用于修饰方法和语句块。
volatile修饰的变量可以为空,同步块的monitor不能为空。

对原子性的保证

volatile无法保证原子性
synchronizde能够保证。因为无法被中途打断。

对可见性的保证

都可以实现共享资源的可见性,但是实现的机制不同
synchronized借助于JVM指令monitorenter和monitorexit,通过排他的机制使线程串行通过同步块,在monitor退出后所共享的内存会被刷新到主内存中。
volatile使用机器指令(硬编码)的方式,lock迫使其他线程工作内存中的数据失效,不得不主内存继续加载。

对有序性的保证

volatile关键字禁止JVM编译器已及处理器对其进行重排序,能够保证有序性。
synchronized保证顺序性是串行化的结果,但同步块里的语句是会发生指令从排。

其他

volatile不会使线程陷入阻塞
synchronized会会使线程进入阻塞。

最新文章