Java并发机制(六)—— 线程安全

当多个线程共享一个变量时,考虑到线程安全,要保证下面3个要素:

  • 原子性(synchronizedLock
  • 可见性(volatilesynchronizedLock
  • 有序性(volatilesynchronizedLock

由于synchronizedLock可以保证某个时刻只有一个线程执行同步代码,所以是线程安全的,但是会影响效率。

当在处理并发编程的时候,只要程序满足了原子性,可见性和有序性,就不会发生脏数据的问题。

1 有序性

有序性:程序执行的顺序按照代码的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。可以通过volatile关键字来保证一定的“有序性”。

2 可见性

可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其它线程能够立即看得到修改的值。

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到内存,当有其它线程需要读取共享变量时,它会去内存中读取新值。

普通的共享变量不能保证可见性,因为普通共享变量被修改后,什么时候被写入内存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

更新内存的步骤:当前线程将其它线程的工作内存中的缓存变量的缓存行设置为无效,然后当前线程将变量的值跟新到内存,更新成功后将其它线程的缓存行更新为新的内存地址。其它线程读取变量时,发现自己的缓存行无效,它会等待缓存行对应的内存地址被更新后,然后去对应的内存读取最新的值。

3 原子性

原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。在Java中,基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。

atomic [əˈtɑːmɪk] 原子的,原子能的;微粒子的 operation [ˌɑːpəˈreɪʃn] 操作;经营;[外科] 手术;运算

相关的术语:

术语

3.1 Java中的原子操作

Java中可以通过锁和循环CAS的方式来实现原子操作

3.1.1 使用循环CAS实现原子操作

自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止。

JDK 1.5的并发包里提供了一些类来支持原子操作,如tomicBoolean(用原子方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)。这些原子包装类还提供了有用的工具方法,比如以原子的方式将当前值自增1和自减1

CAS虽然很高效地解决了原子操作,但是CAS仍然存在三 大问题:

  • ABA问题。 因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面 追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。从 JDK 1.5开始,Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值;
  • 循环时间长开销大。 自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销;
  • 只能保证一个共享变量的原子操作。 当对一个共享变量执行操作时,可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。还有一个办法,就是把多个共享变量合并成一个共享变量。比如,有两个共享变量i = 2,j = a,合并一下ij = 2a,然后用CAS来操作ijJDK 1.5提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作;
3.1.2 使用锁机制实现原子操作

锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁、轻量级锁和互斥锁。除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。

3.2 Atomic

我们经常使用的i++操作并不是线程安全的,这时通常会使用synchroized关键字来处理并发操作,在并发量不大的情况使用synchroized性能并不是特别高。

JDK 1.6以前synchroized是重量级锁,无论有没有资源竞争都会对变量加锁,在JDK 1.6之后引入了偏向锁和轻量级锁,效率才有了很大的提升。

atomic类使用了CAS的思想,只有真正资源竞争的时候才会有资源消耗,而且Atomic是通过底层硬件指令集实现的,所以并发量不大的情况下性能更高。

主要原理就是CAS(比较和交换),涉及到三个值(VON),V是内存中真正的值,O是加载到线程中的预期值,N是计算后的目标结果值,当计算出目标结果值时比较VO是否相等,不相等代表V被其他线程改写过,那么将V重新赋值给O,然后重新计算目标值,再次重复上述步骤,这个称为自旋操作。

缺点:

  • 存在ABA问题,因为每次都比较OV的值,如果在比较之前V被多次改写过,最终的值还是之前的V,那么仅仅比较最终的VO是无法知道这种情况的;
  • 只能针对一个共享变量进行原子操作;
  • 可以看到当VO不等的时候就需要自旋操作,当并发数量很多,资源竞争激烈时,进行自旋操作等待的时间会很长,性能会大幅度降低,这时候使用其他锁会比较合适;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值