如何实现并发的原子性、可见性、有序性

原子性

是指一个操作是不可中断,即使多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰;简单看几个例子:

1)int x=10; 2)int y=x; 3)x++; 4.x=x+1;

这上面四个语句,区分哪些是原子操作,哪些非原子操作?

1)第一句,直接把10赋值给x的工作内存,这个操作是不可再分的,并且很直接,这就是原子操作;

2)第二句,先读取x的值,然后将x的值写入到y的工作内存,这个可分割,并且不直接,这就不是原子操作 ;

3)第三句,其实和第四句一样都是执行x+1;先读取x的值,然后进行+1操作,才会将新值写入x的工作内存;

所以可以看出来,所谓的原子操作,原子性就是直接对一个变量进行操作,中间没有别的操作(实际指的是操作不可分割性);

原子性-Atomic包

  • AtomicXXX:CAS、Unsafe.compareAndSwapInt。
  • AtomicLong、LongAdder。
  • AtomicReference、AtomicReferenceFieldUpdater。
  • AtomicStampReference:解决CAS的ABA问题。

原子性-锁

  • synchronized:依赖JVM。
  • Lock:依赖特殊的CPU指令,代码实现,ReentrantLock。

原子性-synchronized

  • 修饰代码块:大括号括起来的代码,作用于调用的对象。
  • 修饰方法:整个方法,作用于调用的对象。
  • 修饰静态方法:整个静态方法,作用于所有对象。
  • 修饰类:括号括起来的部分,作用于所有对象。

原子性-对比

  • synchronized:不可中断锁,适合竞争不激烈,可读性好。
  • Lock: 可中断锁,多样化同步,竞争激烈时能维持常态。
  • Atomic: 竞争激烈时能维持常态,比Lock性能好,只能同步一个值。

可见性

是指当线程1修改某一个共享变量的值,线程1修改完成以后,线程2能够立即知道修改完成后共享变量的值,那么说明是可见的;可见性就是说线程之间操作共享变量时,彼此之间都知道;

导致共享变量在线程间不可见的原因

  • 线程交叉执行。
  • 重排序结合线程交叉执行。
  • 共享变量更新后的值没有在工作内存与主存间及时更新。

可见性-synchronized

  • JMM(Java内存模型)关于synchronized的两条规定:

    • 线程解锁前,必须把共享变量的最新值刷新到主内存。
    • 线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁是同一把锁)。

可见性-volatile
通过加入内存屏障和禁止重排序优化来实现。

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存。
  • 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。

注意:volatile不具有原子性。

volatile的使用场景

  • 对变量的写操作不依赖于当前值。
  • 该变量没有包含在具有其他变量的不必要的式子中。

有序性

有序性就是指执行代码是有序去执行,线程1先执行,完了之后线程2再去执行;
有序性-happens-before原则:

(1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。
(2)锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作。
(3)volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。
(4)传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。
(5)线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
(6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
(7)线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。
(8)对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始。


如何去实现

原子性

  • Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

可见性

Java提供了volatile关键字来保证可见性。
  当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
  而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
  另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

有序性

  • 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

  • 在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

  • 另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值