JMM——并发编程的常见问题

并发编程的常见问题

通过上一篇 JMM初识 我们充分了解什么是JMM,JMM的作用。但是JMM,在并发编程的情况下依然会存在问题。

并发编程的常见问题分类

  • 原子性问题
  • 有序性问题
  • 可见性问题

1.原子性问题

 原子性指的是一个操作不可中断,即使是在多线程的环境下,一个操作一旦开始就不会被其他线程所影响。

ps: 在32位操作系统中,(byte,short,int,float,boolean,char)基本数据类型的读写操作是原子性的,但是对于long,double类型的数据操作非原子性的。因为32位的操作系统一次处理32位的数据,而long和double类型属于64个字节,需要两部操作。但是在64位操作系统中,即使64位数据被两个线程分为两次读取,也无需担心。因为基本在目前的虚拟机中,几乎把64位的数据读写操作都作为原子操作来执行。

	x=10; // 原子操作
	y=x; // 变量之间的相互赋值,不是原子操作
	x++; // 对变量进行计算操作,并且赋值,非原子性操作。
	x= x+1

2.可见性问题

  可见性指当一个线程修改了主内存中共享变量的值,其他线程能否立刻感知到这个修改的值。
可见性问题,在串行常见下是不存在的,即使上一段代码进行值得修改,下一段代码依然能够读取到最新的值。但是在多线程的场景下,就会普遍的发送。因为线程操作的是工作内存中的值,且CPU是无序执行的。例如线程A读取变量x的值为1,然后修改为x=2,这个时候线程B读取变量x的值,并且修改为3。无论是线程A或者线程B刷回到内存中,必然会导致这个变量的值是不可信的。

3.有序性问题

  有序性是指对于单线程的执行代码,我们总是认为代码的执行是按照顺序依次执行的。
这样的理解没有错,CPU在进行指令重排序的前提,保证结果一致性,所以基本在单线程不会造成任何的问题。但是在多线程的环境下,CPU上下文切换是无序且具有随机性的,并且java代码在编译阶段,解析阶段,CPU执行阶段都会进行指令的重排序,所以无法保证代码多个线程代码的执行顺序。
在这里插入图片描述

解决常见并发问题

在开发多线程的java程序的过程中,保证系统的原子性,有序性,可见性,系统才属于一个安全的。

1.如何解决原子性问题

 JVM在自身提供了对基本数据类型读写操作,在此基础上,自己开发的代码可以通过synchronize和Lock锁实现原子性。因为synchronize和Lock锁能够保证任一时刻只有一个线程访问该代码。

2.如何解决可见性问题

 volatile关键字保证可见性。当一个共享的变量被volatile修饰后,它会保证修改的值立即被其他线程所感知,即修改的值立即更新到主内存中,当其他线程需要读取的时候,它会去主内存中读取新值。synchronize 和Lock 也可以保证可见性,因为被synchronize修饰的Lock的代码片段成为同步代码块,只能被一个线程所使用执行。即同一个时间段只能有一个线程访问共享变量,并在其释放锁之前将修改的变量刷新到内存中。

3.如何解决有序性问题

 两种情况会造成有序性的问题。

  • 线程上下文切换:CPU在执行我们代码的时候,线程A与线程B是无序的在进行切换执行的。代码可能在线程A中执行了一半,然后切换到线程B来执行。这就造成了我们代码执行的无序性,我可以通过synchronize和Lock锁对同步的代码块进行加锁操作,保证这块代码在某一时刻,只能被一个线程来执行。也就是我们利用原子性来保证程序的有序性。
  • 指令重排序:java的代码最终是解析成一个一个的指令在运行(也称为java汇编指令)。java编译器,java解析器,CPU为了提升程序的效率,都会直接或者间接对指令进行重排序操作。当然,无论如何指令重排序都需要遵守as-if-serial原则。我们可以通过volatile关键字来禁止指令重排序。 针对此类问题,当然Java语言规定JVM必须遵守happens-before原则。

happens-before原则

  1. 程序顺序原则,即在一个线程内必须保证语义的串行性,也就是说按照代码顺序执行。
  2. 锁规则 解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)。
  3. volatile规则 volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中毒该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值。
  4. 线程启动规则 线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见。
  5. 传递性A先于B,B先于C 那么A必然先于C。
  6. 线程终止规则 线程的所有操作先于线程的终结,Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法返回后,线程B对共享变量的修改将对线程A可见。
  7. 线程中断规则 对线程interrupt()方法的调用先行发生于被中断线程的代码检查到中断时间的发生,可以通过Thread.interrupted()方法检查线程是否中断。
  8. 对象终结规则对象的构造函数执行,结束先于finalize()方法。

as-if-serial原则

  as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial原则。
  为了遵守as-if-serial原则,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值