JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS

JUC简介

    • 1. JUC简介
    • 2. 原子类与CAS
      • 2.1 Atomic包
      • 2.2 CAS介绍
      • 2.3 CAS原理详解
      • 2.4 CAS缺陷

转自 极客时间

1. JUC简介

从JDK1.5起,Java API 中提供了java.util.concurrent(简称JUC)包,在此包中定义了并发编程中很常用 的工具,比如:线程池、阻塞队列、同步器、原子类等等。JUC是 JSR 166 标准规范的一个实现,JSR 166 以及 JUC 包的作者是同一个人 Doug Lea 。

JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第1张图片

2. 原子类与CAS

通过上面学习volatile,我们发现volatile修饰的变量存在原子性的BUG,这个问题怎么解决呢?难道只能使用Synchronized吗?

2.1 Atomic包

java.util.concurrent.atomic包
从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提 供了一种用法简单、性能高效、线程安全地更新一个变量的方式。可以解决volatile原子性操作变量的问 题。

因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别 是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都 是使用Unsafe实现的包装类。

JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第2张图片

AtomicInteger主要API如下:

get() //直接返回值
getAndAdd(int) //增加指定的数据,返回变化前的数据
getAndDecrement() //减少1,返回减少前的数据
getAndIncrement() //增加1,返回增加前的数据
getAndset(int) 增加1,返回增加前的数据

addAndGet(int) //设置指定的数据,返回设置前的数据
decrementAndGet() //减少1,返回减少后的值
incrementAndGet() //增加1,返回增加后的值
1azyset(int) //仅仅当get时才会set
compareAndset(intint) //尝试新增后对比,若增加成功则返回true否则返回false

用AtomicInteger解决可见性案例中的问题
JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第3张图片

2.2 CAS介绍

JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第4张图片

2.3 CAS原理详解

Java中对CAS的实现

Java不能像C/C++那样直接操作内存区域,需要通过本地方法(native 方法)来访问。JAVA中的CAS操作都 是通过sun包下Unsafe类实现,而Unsafe类中的方法都是native方法

Unsafe类,全限定名是sun.misc.Unsafe,位于在 sun.misc 包下,不属于Java 标准API。 Unsafe对CAS操作的实现有三个

/**
 * Atomically update Java variable to x if it is currently holding expected.
 * @return true if successful
 */
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
/**
 * Atomically update Java variable to x if it is currently holding expected.
 * @return true if successful
 */
public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);
/**
 * Atomically update Java variable to x if it is currently holding expected.
 * @return true if successful
 */
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);

我们以其中的compareAndSwapInt为例,来说明

compareAndSwapInt

  • 方法作用:如果当前时刻,待更新的原值 与 预期值 expected相等,则将 待更新的原值 的 值更新 为x。如果更新成功,则返回 true,否则返回 false。
  • compareAndSwapInt是 Unsafe 类中提供的一个原子操作。方法一共有四个参数:
    1.o:需要改变的对象
    2.offset:内存偏移量,offset 为o对象所属类中,某个属性在类中的内存地址偏移量
    3.expected :预期值
    4.x:拟替换的新值

内存偏移量offset的作用是什么?

  • 计算出对象中,待更新的原值的准确内存地址
  • Java对象在内存中会占用一段内存区域,Java对象的属性会按照一定的顺序在对象内存中存储。根 据对象this就可以定位到this对象在内存的起始地址,然后在根据属性state(相对this)的offset内存 偏移量,就可以精确的定位到state的内存地址,从而得到当前时刻state在内存中的值。

JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第5张图片
JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第6张图片

2.4 CAS缺陷

CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个地方:循环时间太长、只能保证一个共享变量原子操作、ABA问题。

  • 循环时间太长:如果CAS一直不成功呢?如果自旋CAS长时间地不成功,则会给CPU带来非常大的开销。
    原子类AtomicInteger#getAndIncrement()的方法
  • 只能保证一个共享变量原子操作:看了CAS的实现就知道这只能针对一个共享变量,如果是多个共 享变量就只能使用锁了。
  • ABA问题:CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情 况:如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但 是实质上它已经发生了改变,这就是所谓的ABA问题。对于ABA问题其解决方案是加上版本号,即 在每个变量绑定一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。

下面我们将通过一个例子可以可以看到AtomicStampedReference和AtomicInteger的区别。我们定义 两个线程,线程1负责将100 —> 101 —> 100,线程2执行 100 —>2022,看两者之间的区别。
JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第7张图片
JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第8张图片
运行结果充分展示了AtomicInteger的ABA问题和AtomicStampedReference解决ABA问题。JUC【1.原子类、2.锁Lock、3.阻塞队列、4.并发集合容器、5.并发工具类、6.线程池】、原子类、CAS_第9张图片

你可能感兴趣的:(java)