Java中Atomic原子类型的详细讲解(一)-刘宇
作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。
一、什么是原子类型
- 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。
- Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
- Atomic包大致可以属于4种类型的原子更新方式,分别是
- 原子更新基本类型
- 原子更新数组
- 原子更新引用
- 原子更新属性
二、原子类型的实现原理
Atomic包里的类基本都是使用Unsafe实现的包装类,从而达到了原子性的操作。然后通过将内部的value变量用volatile关键字修饰,从而达到了可见性、防止重排序、原子性。
三、利用volatile关键字演示原子性问题
演示
使用三个线程分别对同一个变量进行加1操作,原本理想的结果应该是输出1500个元素,而真实结果却只输出了1498个,原因是有线程输出了同样的结果。是因为volatile关键字可以添加内存屏障有效防止重排序、内存可见性,但是不能确保其原子性,而value+=1时,看似是一行代码,实则是分成了多步执行的。
package com.brycen.concurrency03.atomic;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
private static volatile int value = 0;
private static Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int x= 0;
while(x<500) {
int temp = value += 1;
set.add(temp);
System.out.println(Thread.currentThread().getName()+":"+temp);
x++;
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int x= 0;
while(x<500) {
int temp = value += 1;
set.add(temp);
System.out.println(Thread.currentThread().getName()+":"+temp);
x++;
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int x= 0;
while(x<500) {
int temp = value += 1;
set.add(temp);
System.out.println(Thread.currentThread().getName()+":"+temp);
x++;
}
}
});
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}
}
输出结果:
我们可以看到,原本应该输出1500个,但是结果却只输出了1498个。这是因为volatile关键字可以添加内存屏障有效防止重排序、内存可见性,但是不能确保其原子性,而value+=1时,看似是一行代码,实则是分成了多步执行的
为什么多个线程会输出相同的结果
volatlie关键字没有原子性,那么value+=1这行代码实则会被分为4步执行
- 获取value的值
- 将获取到的值+1
- 将最新值赋值给value
- 将value的值刷入内存
假设当时value值为1,当线程1执行完+=操作的第1步时,cpu执行权被线程2抢走,然后线程2执行+=操作,直至输入内存,并输出2,这时cpu执行被线程1抢走,继续执行没有完成的+=操作,那么这时线程1会根据第一步拿到的1进行+1操作,那么返回输出的同样是2。
四、利用AtomicInteger解决原子性问题
package com.brycen.concurrency03.atomic;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
private static Set<Integer> set = Collections.synchronizedSet(new HashSet<Integer>());
private static AtomicInteger value = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int x= 0;
while(x<500) {
int temp = value.getAndIncrement();
set.add(temp);
System.out.println(Thread.currentThread().getName()+":"+temp);
x++;
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int x= 0;
while(x<500) {
int temp = value.getAndIncrement();
set.add(temp);
System.out.println(Thread.currentThread().getName()+":"+temp);
x++;
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int x= 0;
while(x<500) {
int temp = value.getAndIncrement();
set.add(temp);
System.out.println(Thread.currentThread().getName()+":"+temp);
x++;
}
}
});
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}
}
输出结果:
五、AtomicInteger的基本使用
1、AtomicInteger的创建和get()方法
-
AtomicInteger的创建分为两种:
- 无参的,默认值0
- 有参的,指定默认值
-
get():用于获取当前值,该方法是不需要锁的,因为他只是去取值
AtomicInteger i = new AtomicInteger();
System.out.println(i.get());//输出0
AtomicInteger j = new AtomicInteger(10)
System.out.println(j.get());//输出10
2、set()
- 该方法是不需要锁的,因为set方法相当于初始化该变量了
AtomicInteger i = new AtomicInteger();
i.set(12);
System.out.println(i.get());//输出12
3、getAndSet(int)
- 先取值,再设置值。
AtomicInteger i = new AtomicInteger();
int result = i.getAndSet(10);
System.out.println(result);//输出0
System.out.println(i.get());//输出10
4、getAndAdd(int)
- 先拿到值并且后加num。
AtomicInteger i = new AtomicInteger(10);
int result = i.getAndAdd(10);
System.out.println(result);//输出10
System.out.println(i.get());//输出20
5、addAndGet(int)
- 先自动加num,再取值。
AtomicInteger i = new AtomicInteger(10);
int result = i.addAndGet(10);
System.out.println(result);//输出20
System.out.println(i.get());//输出20
6、getAndIncrement()
- 先拿到值然后再+1
AtomicInteger i = new AtomicInteger();
int result = i.getAndIncrement();
System.out.println(result);//输出0
System.out.println(i.get());//输出1
7、incrementAndGet()
- 先拿到值然后再+1
AtomicInteger i = new AtomicInteger();
int result = i.incrementAndGet();
System.out.println(result);//输出1
System.out.println(i.get());//输出1
9、getAndDecrement()
- 先拿到值然后再-1
AtomicInteger i = new AtomicInteger(10);
int result = i.getAndDecrement();
System.out.println(result);//输出10
System.out.println(i.get());//输出9
10、decrementAndGet()
- 先-1再取值
AtomicInteger i = new AtomicInteger();
int result = i.decrementAndGet();
System.out.println(result);//输出9
System.out.println(i.get());//输出9
11、compareAndSet(int expect, int update)
- 快速失败策略,是用于判断期望值是否与变量实际值相等,如果相等则将update赋值给变量,否则失败。
//成功案例
AtomicInteger atomicInteger = new AtomicInteger(10);
boolean result = atomicInteger.compareAndSet(10, 12);
System.out.println(result);//输出true
System.out.println(atomicInteger.get());//输出12
//失败案例
AtomicInteger atomicInteger1 = new AtomicInteger(10);
boolean result1 = atomicInteger1.compareAndSet(11, 12);
System.out.println(result1);//输出false
System.out.println(atomicInteger1.get());//输出10
六、AtomicBoolean的基本使用
只有两种值,为0和1,即真或假。
1、AtomicInteger的创建和get()方法
-
AtomicInteger的创建分为两种:
- 无参的,默认值0
- 有参的,指定默认值
-
get():用于获取当前值,该方法是不需要锁的,因为他只是去取值
AtomicBoolean bool = new AtomicBoolean();
System.out.println(bool.get());//输出false
AtomicBoolean bool = new AtomicBoolean(true);
System.out.println(bool.get());//输出true
2、set()
- 该方法是不需要锁的,因为set方法相当于初始化该变量了
AtomicBoolean bool = new AtomicBoolean();
bool.set(true);
System.out.println(bool.get());//输出true
3、getAndSet(int)
- 先取值,再设置值。
AtomicBoolean bool = new AtomicBoolean(true);
boolean result = bool.getAndSet(false);
System.out.println(result);//输出true
System.out.println(bool.get());//输出false
4、compareAndSet(boolean expect, boolean update)
- 快速失败策略,是用于判断期望值是否与变量实际值相等,如果相等则将update赋值给变量,否则失败。
//成功案例
AtomicBoolean bool = new AtomicBoolean(true);
boolean result = bool.compareAndSet(true, false);
System.out.println(result);//输出true
System.out.println(bool.get());//输出false
//失败案例
AtomicBoolean bool1 = new AtomicBoolean(true);
boolean result1 = bool1.compareAndSet(false, true);
System.out.println(result1);//输出false
System.out.println(bool1.get());//输出true
七、AtomicBoolean的作用
可以当做多线程中的开关flag,从而来代替sychronic这样比较重的锁
案例
- 如果这个案例中使用基本数据类型的boolean作为标志位,则线程永远不会停止。
package com.brycen.concurrency03.atomic;
import java.util.concurrent.atomic.AtomicBoolean;
public class AtomicBooleanFlag {
private static AtomicBoolean flag = new AtomicBoolean(true);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
while (flag.get()) {
i++;
}
System.out.println("i="+i);
}
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
flag.set(false);
}
}
输出
i=128183323
八、AtomicLong的基本使用
他基本上与AtomicInteger的使用方法差不多,这边就不演示了,简单说一下他们的区别
AtomicLong与AtomicInteger的区别
- AtomicLong比AtomicInteger多了一个VM_SUPPORTS_LONG_CAS的boolean变量,对应的值是VMSupportsCS8(),这个是由JVM调用的。
VMSupportsCS8()方法的作用
- 因为long类型的数据是64位的,那么在32位CPU中,就需要分高位和低位获取一个long类型的数据,那么就会无法保证原子性的操作。VMSupportsCS8()这个方法就是判断该jvm、cpu是否支持long类型的lockless的CompareAndSet,如果支持,则VMSupportsCS8()方法就不会对数据总线进行加锁了,如果不支持则需要对数据总线进行加锁来保证原子性。
九、compareAndSwap算法(CAS)详解
其实在原子类型中涉及到改变变量数值的操作都会进行CAS算法校验。它是利用Unsafe发送汇编指令达到无锁的状态,其实他是有锁的,只不过他是CPU级别的,不是系统级别的,性能会非常高。它会对比我们的当前值是否和变量值一致,如果一致才会将最新值赋值给变量,否则会快速失败,然后进行循坏尝试。
下面是Integer原子类型中改变数值时的部分源代码,其中compareAndSet中就用到了CAS算法
for(;;){
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
步骤分析
时序 | 线程1 | 线程2 |
---|---|---|
int current = get(); | current=1 | current=1 |
int next = current + delta; | next=2 | next=2 |
if (compareAndSet(current, next)) | 释放CPU执行权 | current==value:判断变量的值value和当前值current是否一致。一致则将要next的值赋值给value |
if (compareAndSet(current, next)) | current==value:判断变量的值value和当前值current是否一致。而线程1中的current值为1,value已经变为2,所以不等,则继续循环,直至相等 | 释放CPU执行权 |
十、利用compareAndSet方法来解决synchronized会使其他线程block住无法干其他事情的现象
1. 自定义一个异常
package com.brycen.concurrency03.atomic;
public class GetLockException extends Exception {
public GetLockException() {
super();
}
public GetLockException(String message) {
super(message);
}
}
2. 自定义一个利用compareAndSet算法的锁
package com.brycen.concurrency03.atomic;
import java.util.concurrent.atomic.AtomicInteger;
public class CompareAndSetLock {
private final AtomicInteger value = new AtomicInteger();
private Thread currentThread = null;
public void tryLock() throws GetLockException{
boolean success = value.compareAndSet(0, 1);
if (!success) {
// 不相等则抛出异常,表示有其他线程使用
throw new GetLockException();
}
currentThread = Thread.currentThread();
}
public void unLock() {
if (0 == value.get()) {
return;
}
// 确保解锁的是拿到锁的那个线程
if (currentThread == Thread.currentThread()) {
value.compareAndSet(1, 0);
}
}
}
3. 自定义一个利用compareAndSet算法的锁
package com.brycen.concurrency03.atomic;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerDetailTest2 {
private static CompareAndSetLock lock = new CompareAndSetLock();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
doSomeThing2();
}
}).start();
}
}
//这个方法是利用synchronized实现的锁,会使其他线程进入block状态
private static void doSomeThing() {
synchronized (AtomicIntegerDetailTest2.class) {
System.out.println(Thread.currentThread().getName()+": get the lock");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private static void doSomeThing2() {
try {
lock.tryLock();
System.out.println(Thread.currentThread().getName()+": get the lock");
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GetLockException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
System.out.println(Thread.currentThread().getName()+": do other things");
}finally {
lock.unLock();
}
}
}
输出结果
Thread-0: get the lock
Thread-3: do other things
Thread-4: do other things
Thread-1: do other things
Thread-2: do other things