三、并发编程三大特性之一:原子性

94 阅读3分钟

原子性

原子性概念

原子性:一个或者一组操作不可被分割,不可被中断,不受其他线程影响

如何保证原子性

使用synchronized关键字

  • 使用synchronized关键字修饰方法或者代码块
  • synchronized是在进入临界区域之前添加了锁,导致其他线程无法进入,以此保证原子性
  • synchronized加锁的指令是monitorenter,释放锁的指令是monitorexit

CAS(compare and swap)

CAS不是java编程语言实现的,而是CPU层面的并发原语

CAS的机制是,将主内存中的共享变量读到工作内存中,对其进行修改。在写回主内存之前,先读取主内存中变量的值,并且对变量所在的缓存行加锁,保证其它线程无法对变量进行修改。如果主内存中的变量值与修改前的变量值相同,才将新值写回主内存。整个过程都是不能被中断的,确保了操作的原子性。

如果发现主内存中变量值被修改,会重新读取数据,计算得到新值,再执行CAS操作。

如何使用CAS

在Java中,unsafe类中封装了CAS操作,但是使用起来还是比较麻烦,所以大牛们在此基础上又进行了封装,提供了大量的原子类,例如AtomicInteger等等。这些原子类的方法都是原子性的。

CAS的缺点
  • CAS只能保证一个变量的原子性,无法像synchronized一样保证多行代码的原子性
CAS的问题
ABA问题

三个线程操作共享变量A,第一个线程将变量从A变成B,第二个线程将变量从B变成A,此时第三个线程发现共享变量的值还是A,以为没有被操作过,然后将新值写到主内存中,这是不允许的

解决方法是给共享变量添加个版本号,每次修改后,版本号自增,并且每次比较共享变量值时,版本号也要比较

自旋时间过长
  • 设置自旋次数,超过这个次数,结束自旋
  • 将对共享变量的操作暂时存储起来,等到需要获取结果时,再全部执行

ReentrantLock锁

ReentrantLock锁是通过AQS机制实现原子性。在并发量特别大的情况下,推荐使用ReentrantLock锁

ReentrantLock锁相较于synchronized,功能更强大

ThreadLocal

ThreadLocal是通过让每个线程有自己的数据,通过操作自己的数据,避免发生并发问题

ThreadLocal实现原理
  • 每个线程都存储一个成员变量,ThreadLocalMap
  • ThreadLocal本身不存储数据,更像是一个工具类,提供方法操作ThreadLocalMap
  • ThreadLocalMap是基于Entry数组实现的。因为一个线程可以绑定多个ThreadLocal,如此可能会存储多个数据
  • 每个线程都有自己的ThreadLocalMap,Map中的数据key值是ThreadLocal对象
  • ThreadLocalMap的key是弱引用,而key指向ThreadLocal对象,也就是说,如果ThreadLocal对象只有key指向的话,那么会被GC回收掉。这里设置成弱引用是避免ThreadLocal的其他引用不存在时,不会被回收掉,此时就无法通过变量获取到ThreadLocal对象了,如此就发生内存泄漏
  • 因为ThreadLocalMap的引用是线程对象,如果ThreadLocal引用消失,ThreadLocal被回收,但是线程对象还存在,那么ThreadLocalMap中的value就不会被回收,也无法获取到,这里也存在内存泄漏。解决办法是执行ThreadLocal对象提供的remove()方法,移除Entry对象

image.png