Atomic:
Atomic包是java.util.concurrent下的另一个专门为线程安全设计的java的包,包含多个原子性操作的类。基本特性就是在多线程情况下,当多个线程想要同时操作这些类的某些实例方法时,具有排他性,也就是当某个线程在执行某个方法时,不会被其他线程打断,其他线程会在外部等待,一直等到该方法执行完毕,才由JVM从等待队列中选择另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(只是在硬件级别去阻塞了)。可以对基本数据,数组中的基本数据,对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读写操作。
我们先看一下传统的锁是怎样保证线程安全的
class LockDemo {
private int a;
public synchronized void setA(int b) {
this.a = b;
}
public synchronized int getA() {
return a;
}
public synchronized void addA() {
++a;
}
public synchronized void reduceA() {
--a;
}
}
其实这样的synchronized已经能满足我们日常的线程安全需求了,synchronized是基于代码阻塞的机制,也就是当某个线程占用资源时,其他线程是无法进入的,如果这个线程出现问题的时候,出现大量线程阻塞,CPU就会耗费大量资源来处理阻塞在外的这些线程,但是CPU的任务本不该如此,还极可能出现死锁等问题,对于这样的简单操作反而显得有些笨重,所以应该有更合适更高效的方法来处理这样的问题。所以就有了CAS
Compare and swap(CAS)
当前的处理器基本都支持CAS,这是一种基于硬件的处理,每一个CAS操作都包含三个运算符:内存地址V , 一个期望值A和新值B,操作的时候如果这个地址上存放的值等于期望的值A,那么就赋值为B,如果没有,那么就不做任何操作。简而言之,你的值和我的期望值相等,就给你新值,否则不干活了。
我们自己可以简单的模拟一下CAS的实现
class CASDemo {
private int value;
public synchronized int getValue() {
return value;
}
public synchronized int cas(int expectValue , int newValue) {
int oldValue = value;
if(value == expectValue) {
value = newValue;
}
return oldValue;
}
}
class CASTest {
private CASDemo casDemo;
public int getValue () {
return casDemo.getValue();
}
public int add () {
int oldValue = casDemo.getValue();
while(casDemo.cas(oldValue , oldValue + 1) != oldValue) {
oldValue = casDemo.getValue();
}
return oldValue + 1;
}
}
看下Atomic包
看一下AtomicInteger类是怎么处理自增的处理的,也就是方法getAndIncrement()
这里调用了一个叫做Unsafe类的方法,这个类比较特殊内部大多是native的方法,而且这个类也不允许我们随意使用,当然JDK自己是可以用的,(ps:处于凡是试试的态度,我试了一下调用它的方法,报错如下)
看一下valueOffset是哪来的
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
这里又调用了Unsafe类的一个方法,参数是通过反射获取的AtomicInteger的value属性,也就是它的值
继续进
public native long objectFieldOffset(Field var1);
很可惜,是一个本地方法,查看文档可知,这个方法返回的就是"原来的值"的内存地址 , valueOffset的值(ps:文档是其他某位大神那里借来的)
返回一开始的方法 点进去看
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
参数比较混乱 var2就是valueOffset 继续跟进
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
喔,又是一个本地方法,参数很乱 按我们之前的讲就是:
var2(valueOffset) , var4(expectValue) , var5(newValue)
我再借一点注释
这个方法原子的将变量的值更新为var5,如果成功了返回true。这样返回去取反即为false,循环结束,返回var5,取到新值。
当然Unsafe关于CAS的方法都是本地方法,是由C语言实现的,我们这里是看不到具体实现细节的。
说了半天 到底CAS是怎么保证线程安全的呢,其实在语言层次我们是没有做任何关于同步的操作的,也没有任何锁。Atomic包下这些类将这些操作都交给了CPU和内存,利用CPU多处理的能力,实现硬件的阻塞,再加上volatile变量的特性即可实现基于原子性的操作的线程安全。所以CAS不是没有阻塞 ,只是阻塞不是语言层面,而是在硬件层面,这样便会更高效。