原子性
原子性概念
原子性:一个或者一组操作不可被分割,不可被中断,不受其他线程影响
如何保证原子性
使用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对象