一 概述
- 在Java的并发编程中,如果在多线程执行下,在同一时间操作共享变量,很容易出现安全问题。如执行i++,很可能会得到不正确的值
- 通常情况我们的解决办法是通过synchronized关键字来控制线程的执行顺序,虽然可能得到正确的值,但是由于synchronized使用的悲观锁的方式,性能比较低下
- 在atomic包下,Java提供了一些列的原子类,来保证多线程安全的更新这些基本类型的变量,数组元素,引用类型,以及更新对象中的字段类型
- 原子类采用的乐观锁的策略,在Java中则是使用了CAS操作实现
-
二 原子类介绍
2.1 操作基本类型的原子类
在 atomic 包下,涉及到基本数据的原子操作类有 3 个:AtomicInteger,AtomicLong,AtomicBoolean。这个三个类分别提供了对整型,长整型,布尔类型的原子操作,以AtomicInteger为例
// 获取当前值,然后自加,相当于i++
getAndIncrement()
// 获取当前值,然后自减,相当于i--
getAndDecrement()
// 自加1后并返回,相当于++i
incrementAndGet()
// 自减1后并返回,相当于--i
decrementAndGet()
// 获取当前值,并加上预期值
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() {
@Override
public void run() {
test.num++;
}
}.start();
}
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public 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() {
@Override
public void run() {
test.num++;
}
}.start();
}
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public 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() {
@Override
public void run() {
test.atomicNum.getAndIncrement();
}
}.start();
}
for (int i = 0; i < 10000; i++) {
new Thread() {
@Override
public 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为例,然后进行比较交换
```java
package 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个元素,并加1
public final int getAndIncrement(int i) {
return (int)AA.getAndAdd(array, i, 1);
}
// 获取第i个元素并减1
public 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 的值,否则不会执行任何操作。