一 概述
- 在Java的并发编程中,如果在多线程执行下,在同一时间操作共享变量,很容易出现安全问题。如执行i++,很可能会得到不正确的值
- 通常情况我们的解决办法是通过synchronized关键字来控制线程的执行顺序,虽然可能得到正确的值,但是由于synchronized使用的悲观锁的方式,性能比较低下
- 在atomic包下,Java提供了一些列的原子类,来保证多线程安全的更新这些基本类型的变量,数组元素,引用类型,以及更新对象中的字段类型
- 原子类采用的乐观锁的策略,在Java中则是使用了CAS操作实现
-
二 原子类介绍
2.1 操作基本类型的原子类
在 atomic 包下,涉及到基本数据的原子操作类有 3 个:AtomicInteger,AtomicLong,AtomicBoolean。这个三个类分别提供了对整型,长整型,布尔类型的原子操作,以AtomicInteger为例
// 获取当前值,然后自加,相当于i++getAndIncrement()// 获取当前值,然后自减,相当于i--getAndDecrement()// 自加1后并返回,相当于++iincrementAndGet()// 自减1后并返回,相当于--idecrementAndGet()// 获取当前值,并加上预期值getAndAdd(int delta)// 获取当前值,并设置新值int getAndSet(int newValue)
使用多线程来操作一个共享对象的int类型的属性,会出现线程安全问题
public class IntegerTest {private int num=0;public static void main(String[] args) throws InterruptedException {new IntegerTest().test1();}// 多线程操作基本整形数据public void test1() throws InterruptedException {IntegerTest test = new IntegerTest();for (int i = 0; i < 10000; i++) {new Thread() {@Overridepublic void run() {test.num++;}}.start();}for (int i = 0; i < 10000; i++) {new Thread() {@Overridepublic void run() {test.num--;}}.start();}// 最后的结果不一定为0,可能是负数,也可能是正数TimeUnit.SECONDS.sleep(5);System.out.println(test.num);}}
使用 AtomicInteger 类来代替int类型,在多线程并发下,就不会有并发问题了
AtomicInteger 可以使用 get() set() 方法获取值和设置值 ```java package com.lz.javase.juc.atomic;
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger;
public class IntegerTest {
private int num = 0;private final AtomicInteger atomicNum = new AtomicInteger();public static void main(String[] args) throws InterruptedException {IntegerTest test = new IntegerTest();test.test1();test.test2();}// 多线程操作基本整形数据public void test1() throws InterruptedException {IntegerTest test = new IntegerTest();for (int i = 0; i < 10000; i++) {new Thread() {@Overridepublic void run() {test.num++;}}.start();}for (int i = 0; i < 10000; i++) {new Thread() {@Overridepublic void run() {test.num--;}}.start();}TimeUnit.SECONDS.sleep(5);System.out.println(test.num);}// 多线程操作原子类整形public void test2() throws InterruptedException {IntegerTest test = new IntegerTest();test.atomicNum.set(0);for (int i = 0; i < 10000; i++) {new Thread() {@Overridepublic void run() {test.atomicNum.getAndIncrement();}}.start();}for (int i = 0; i < 10000; i++) {new Thread() {@Overridepublic void run() {test.atomicNum.getAndDecrement();}}.start();}TimeUnit.SECONDS.sleep(5);System.out.println(test.atomicNum.get());}
}
<a name="YrtXv"></a>## 2.2 操作引用类型的原子类因为上面那些操作基本类型的原子类,只能操作一个基本类型变量,如果想要操作多个变量的话,则需要使用到这三个原子类:**AtomicReference**、**AtomicStampedReference**、**AtomicMarkableReference**1. AtomicReference:普通的操作引用类型变量的原子类1. AtomicStampedeReference:在对引用类型变量进行操作时,带上版本号。这个类将整数值和引用关联起来,可以用于解决原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。1. AtomicMarkableReference:在进行原子操作时,带上标记。该类将bool类型标记和引用关联起来以AtomicReference为例,然后进行比较交换```javapackage com.lz.javase.juc.atomic;import java.util.concurrent.atomic.AtomicReference;public class AtomicReferenceTest {public static void main(String[] args) {AtomicReferenceTest test = new AtomicReferenceTest();test.test1();}public void test1() {AtomicReference<Person> atomicReference = new AtomicReference<>();Person p1 = new Person();p1.setName("p1");Person p2 = new Person();p2.setName("p2");atomicReference.set(p1);atomicReference.compareAndSet(p1, p2);System.out.println(atomicReference.get());}}
2.3 操作数组的原子类
这里说的操作数组,并不是对数组本身的操作,而是对数组元素的操作。主要涉及到三个类:AtomicIntegerArray、AtomicLongArray及AtomicReferenceArray。分别对应整形数组,长整型数组,引用数组
public class AtomicIntegerArray implements java.io.Serializable {// final类型的int数组private final int[] array;// 获取数组中第i个元素public final int get(int i) {return (int)AA.getVolatile(array, i);}// 设置数组中第i个元素public final void set(int i, int newValue) {AA.setVolatile(array, i, newValue);}// CAS更改第i个元素public final boolean compareAndSet(int i, int expectedValue, int newValue) {return AA.compareAndSet(array, i, expectedValue, newValue);}// 获取第i个元素,并加1public final int getAndIncrement(int i) {return (int)AA.getAndAdd(array, i, 1);}// 获取第i个元素并减1public final int getAndDecrement(int i) {return (int)AA.getAndAdd(array, i, -1);}// 对数组第i个元素加1后再获取public final int incrementAndGet(int i) {return (int)AA.getAndAdd(array, i, 1) + 1;}// 对数组第i个元素减1后再获取public final int decrementAndGet(int i) {return (int)AA.getAndAdd(array, i, -1) - 1;}// ... 省略}
二 CAS算法
1)CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。
2)CAS 是一种无锁的非阻塞算法的实现。
3)CAS 包含了 3 个操作数:
- 需要读写的内存值 V
- 进行比较的值 A
- 拟写入的新值 B
4)当且仅当 V == A 时,CAS 通过原子方式用新值 B = V 的值,否则不会执行任何操作。
