继上篇讲完多线程中的可见性,有序性之后,本篇我们来聊一下线程原子性。
原子性
原子性:线程在执行一个操作或者多个操作,一但开始执行,要么执行成功,要么执行失败。在执行的过程中不允许被其他线程打断(不会切换到其他线程)。看下面代码
int a = 1;
原子操作(具有原子性),即使在并发环境下, 同一个时刻只有一个线程操作
count++;
非原子操作,这代码实际分3步操作:
1> 内存中获取count的值
2> 执行count+1操作
3> 加一结果赋值到count变量
不进行额外的设置,此时count++是非原子操作,并发环境下,同一时刻可以多个线程可以执行上面不同的步骤。
原子操作
实现原子操作方式最简单方式使用synchronized同步块或者lock机制
synchronized (this){
count++;
}
try {
lock.tryLock();
count++;
}finally {
lock.unlock();
}
此处我们介绍另外一中原子操作:Java中的Atomic包提供的原子操作
原子操作类:Atomic*
JDK5之后,java提供了提供一套专用于原子操作的api,根据类用的用途大体分为4大类:原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。
基本类型:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
数组类型:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
字段类型:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
引用类型:AtomicMarkableReference,AtomicStampedReference
此处我们仅仅讨论基本类型以及其操作原理
基本类型
先上代码:体验下如何使用
需求:1000个线程对共享变量自增操作, 每一个线程+1
public class Resource implements Runnable {
public int i= 0; //普通成员变量自增
public volatile int j = 0; //使用volatile修饰变量自增
public AtomicInteger ai = new AtomicInteger(); //使用原子操作类自增
public AutoCounter ct = new AutoCounter(); //自定义类使用synchronized限制自增
//自增
public void run() {
try {
Thread.sleep(10); //让效果更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
j++;
ai.incrementAndGet();
ct.count();
}
}
public class AutoCounter {
private int count = 0;
//自增加一
public synchronized void count(){
count++;
}
//获取count
public synchronized int getCount(){
return this.count;
}
}
public class App {
public static void main(String[] args) throws InterruptedException {
Resource resource = new Resource();
//启动1000线程自增1
for (int i = 0; i < 1000; i++){
new Thread(resource).start();
}
Thread.sleep(3000);
System.out.println("普通成员变量i:" + resource.i);
System.out.println("volatile变量j:" + resource.j);
System.out.println("原子操作类变量:" + resource.ai.get());
System.out.println("synchronized变量:" + resource.ct.getCount());
}
}
结果:
普通成员变量i:989
volatile变量j:995
原子操作类变量:1000
synchronized变量:1000
普通成员变量i跟volatile修饰的变量都无法自增到1000, 说明2点:
1>成员变量线程不安全,
2>volatile操作做无法做到原子操作
而原子操作的类与synchronized修饰自定义类可以完成, 那么我们不经要想, 原子操作的类是怎么做到同步的呢?要解释这个,就得从CAS说起
CAS原理
CAS(compare and swap),用于解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存原值(V)、期望值(A)和新值(B)。如果V值等于A值,那么处理器会自动将V值更新为B值。否则,不做任何操作。
明白概念之后,我们看回AtomicInteger 类:
持有3个属性:
value:CAS中的内存原值(V), 在代码层面的显示值
Unsafe : unsafe类一个很特别的类,名字叫“不安全”,可以直接读写内存、获得地址偏移值、锁定或释放线程。
valueOffset:CAS中内存原值(V)的内存地址(偏移量), Unsafe类通过valueOffset值可以直接操作内存(硬件的寄存器内存)
静态代码块:
AtomicInteger 加载进内存之后,直接获取value值在内存的存储位置
自增方法:
incrementAndGet: 委托unsafe对象实现,返回安全的CAS期待值(A), 然后自加1并返回。
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value; //cas中内存位置值-V值
//比较特殊的一个类,直接操作内存
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; //cas中V值的内存地址(偏移量)
static {
try {
//获取对象某个属性的地址偏移值。
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//自增长并返回,由unsafe对象方法实现
public final int incrementAndGet() {
//获取到cas中的内存位置V值, 同时确保正确,然后加一返回
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
AtomicInteger 类的incrementAndGet方法委托Unsafe 进行CAS判断。
Unsafe 定义3个方法实现:
objectFieldOffset:本地方法,直接获取属性对象的内存地址
getAndAddInt:获取通过CAS判断的期望值
compareAndSwapInt:本地方法, 进行CAS判断
public final class Unsafe {
//本地方法,可以认为直接操作内存地址,返回指定属性对象的值
public native long objectFieldOffset(Field var1);
//参数1:原子操作类对象(AtomicInteger )
//参数2:CAS中的内存偏移地址(AtomicInteger value属性内存地址)
//参数3:自增步长
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5; //CAS中内存值(V)
do {
//通过原子操作类对象与value 属性内存地址获取cas中内存值(V)
var5 = this.getIntVolatile(var1, var2);
//循环操作,一直到CAS中的内存原值(V)与期望值一致,才进入下一步操作
//这里可以理解为第一获取的内存中原值var5与下一次获取原值var5一致,
//表示没有其他线程修改内存中的原值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//进行CAS判断
//参数1:原子操作类对象(AtomicInteger )
//参数2:CAS中的内存偏移地址(AtomicInteger value属性内存地址)
//参数3:CAS中的期望值(A)
//参数4:CAS中的更新值(B)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
}
参照上面的源码, 发现没有任何的同步限制,那又怎么就实现了同步操作呢?其实,在代码层面上虽然没做同步限制,但在底层操作中以及包含同步操作的意思在里面了:
底层不同的处理对CAS处理不一样,在X86平台,CPU给部分指令执行加锁操作,实现指令的原子操作,上文提到CAS就是利用CMPXCHG指令实现的,而CMPXCHG指令是原子操作指令,在同一时刻,仅让一个线程执行,那么CAS结果只有2种结果,要么成功,要么失败。如果对于CAS操作执行失败的线程,做循环执行CAS操作,那么一定能够成功。那么就有阻塞概念了。也可实现了同步机制。
到这,本篇就结束了, 有兴趣的朋友可以继续研究其他Atomic类。