Java面试精讲:一飞老师带你再看Java线程(原子性)

问题:请阐述,什么是线程的原子性?

原子性:线程在执行一个操作或者多个操作,一但开始执行,要么执行成功,要么执行失败。在执行的过程中不允许被其他线程打断(不会切换到其他线程)。看下面代码

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*

Java面试精讲:一飞老师带你再看Java线程(原子性)

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类。


分享到:


相關文章: