多线程–06–多线程安全问题解决–03–无锁方式–原子类

  • Post author:
  • Post category:其他


一、基础概念


1.1 乐观锁与悲观锁

1.1.1 乐观锁

  • 乐观锁是

    无锁

    的实现,乐观是相对悲观锁而言的。

  • java中使用“自旋+CAS+volatile +unsafe类”实现乐观锁。
  • 乐观锁适用于

    竞争少


    、多核 CPU 的场景下(读多写少)。

1、如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响;

2、自旋需要CPU参与

1.1.2 悲观锁

正如其名,具有强烈的独占和排他特性。它指的是对数据被外界修改持保守态度,为了避免同时被其他人修改,采取的是“

先取锁再访问

”的策略。synchronized、ReentrantLock都是悲观锁的典型例子。


1.2 自旋锁与阻塞锁

当一个线程拿不到锁的时候,有以下两种基本的等待策略:

  • 策略1:放弃CPU,进入阻塞状态,等待后续被唤醒,再重新被操作系统调度。
  • 策略2:不放弃CPU,空转,不断重试,也就是所谓的“自旋”。
  • 很显然,如果是单核的CPU,只能用策略1。因为如果不放弃CPU,那么其他线程无法运行,也就无法释放锁。但对于多CPU或者多核,策略2就很有用了,因为没有线程切换的开销。
  • 有一点要说明:这两种策略并不是互斥的,可以结合使用。如果拿不到锁,先自旋几圈;如果自旋还拿不到锁,再阻塞,synchronized关键字就是这样的实现策略。

二、原子类(Atomic类)基本介绍


java中,封装了“CAS+volatile +unsafe类 ”的操作,我们称为原子类

,原子类有很多,都以Atomic开头,原子类对应的变量叫原子变量,原子类包括:

  • 原子整形类:AtomicInteger、AtomicLong、、AtomicBoolean;
  • 原子引用类:AtomicReference、AtomicStampedReference、AtomicMarkableReference;
  • AtomicReference:对引用类型做原子操作;

  • 原子类的ABA问题:从 A 改为 B 又改回 A 的情况,叫做ABA问题。


原子整型类、AtomicReference不能感知ABA问题,需要使用AtomicStampedReference、AtomicMarkableReference解决ABA问题。


  • AtomicStampedReference:能知道被改了多少次,能彻底解决ABA问题。

  • AtomicMarkableReference:仅能感知到有没有被修改,能缓解ABA问题但不能彻底解决ABA问题。
  • 原子数组:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;

对数组中的一个元素进行原子操作。注意:不是对整个数组进行原子操作

  • 字段更新器:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater;
  • 利用字段更新器,可以针对对象的属性进行原子操作。
  • 如果一个类是自己编写的,则可以在编写的时候把成员变量定义为Atomic类型。但如果是一个已经有的类,在不能更改其源代码的情况下,要想实现对其成员变量的原子操作,须用到字段更新器。
  • 字段更新器使用的

    限制条件

    :被处理的字段须被volatile修饰,否则会报错。

2.1 原子整型类AtomicInteger使用举例

package com.fuping3.atomic;

import java.util.concurrent.atomic.AtomicInteger;

public class Demo {
    private AtomicInteger atomic=new AtomicInteger(100);


    public void atomicTest(){
        while(true){               //自旋
            int expect=atomic.get();
            int update=expect-10;
            boolean result = atomic.compareAndSet(expect, update);//CAS
            if(result){
                System.out.println("更新成功");
                break;
            }
        }
    }

}

(1)如上代码运行流程如下图:

(2)原子类的CAS函数源码

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

原子类的CAS函数,其实是封装的Unsafe类中的一个native函数compareAndSwapInt,该函数有4个参数:

  • 第一个是对象(也就是AtomicInteger 对象);
  • 第二个是对象的成员变量偏移量(通过对象+偏移量,可以找到是对象的哪个属性);
  • 第三个参数expect是指变量的旧值(是读出来的值,写回去的时候,希望没有被其他线程修改,所以称为expect);
  • 第四个参数update是指变量的新值(修改过的,希望写入的值)。

当expect等于变量当前的值时,说明在修改的期间,没有其他线程对此变量进行过修改,所以可以成功写入,变量被更新为update,返回true,否则返回false。

Unsafe类仅提供三个CAS函数:compareAndSwapInt、compareAndSwapLong、compareAndSwapObject(引用类型CAS);

—-》对boolean的支持是:将boolean转为int;

—-》对double的支持是:依赖double类型提供的一对double类型和long类型互转的函数DoubleAdder



版权声明:本文为na_tion原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。