针对线程安全问题,jdk除提供了加锁的解决方式外还提供了无锁的方式,例如AtomicInteger
这个原子整数类,
无锁并发的线程安全是通过cas来实现的,这一篇文章就来简单分析下AtomicInteger
的源码实现。
一、AtomicInteger的简答使用
先来看一断非线程安全的代码
@Slf4j
public class ThreadTest2 {
static int count=0;
public static void main(String[] args) throws InterruptedException {
/**
* 有一个静态变量count,两个线程分别对其进行相等次数的+1和-1操作,
* 因为++和--操作本身不是原子的,所以最终打印出的res可能不是0,
* 为了看到效果让这段代码整体重复运行100次
*/
for (int k = 0; k < 100; k++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
count++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
count--;
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
log.info("res:{}",count);
}
}
}
为了解决上述代码中多个线程对变量count同时操作的线程安全问题,可以使用加锁的方式去解决,但jdk也提供了一种无锁的方式,即使用原子整数AtomicInteger
来作为count,我们先来看下怎么使用
@Slf4j
public class ThreadTest2 {
//使用AtomicInteger作为计数器
static AtomicInteger count= new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
/**
* 使用AtomicInteger作为计数器,两个线程分别对其进行相等次数的+1和-1操作,
* 因为++和--操作本身不是原子的,所以最终打印出的res可能不是0,
* 为了看到效果让这段代码整体重复运行100次
*/
for (int k = 0; k < 100; k++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//原子整数的自增操作
count.getAndIncrement();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//原子整数的自减操作
count.getAndDecrement();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
log.info("res:{}",count);
}
}
}
上边的代码中多线程同时调用原子整数类提供的自增和自减操作,由原子整数类保证了线程安全,即
getAndIncrement/getAndDecrement这两个操作本身就是原子的。
二、 AtomicInteger源码解读
为什么AtomicInteger的自增和自减操作是原子的可以保证线程安全呢?这是因为它使用了Unsafe
对象
我们来看一下它的源码
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
// Unsafe对象是jdk提供的,其中提供了一些直接操作内存的方法和一些cas方法可以用来控制线程安全
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
// 这里是用Unsafe获取当前类中value这个属性的内存偏移量,
//可以理解成每个对象都会有一个内存地址,而对象中属性的内存地址相对对象本身的偏移量是固定的,
// 知道了一个对象的内存地址,再加上某个属性的地址偏移量就能定位到某个具体的属性,
//这里获取这个内存偏移量是为了后续通过Unsafe类直接操作这个属性
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 原子整数类的value属性
private volatile int value;
//构造方法
public AtomicInteger(int initialValue) {
value = initialValue;
}
//自增操作,可以看到是直接调用的unsafe类的方法,传递了当前对象,内存偏移量,
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//自减操作
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
}
从源码可以看到原子整数类中实现原子自增的关键代码就是调了unsafe类的方法,所以有必要再看下这个类
三、Unsafe类
Unsafe类是java中的一个比较底层的类,其中包含了一些比较底层的操作,例如直接操作内存,cas操作,线程操作等。因为这是一个比较底层的类,所以jdk不允许我们直接使用它,要获取到它的对象只能通过反射来获取到,我们先看下它的源码,了解下上边提到的unsafe.getAndAddInt方法
部分源码,这个类是sun包下的一个类,从这个包名也能看出是一个比较底层的类,如非必要不要自己去使用它。
原子整数类的线程安全是通过unsafe提供的cas操作完成的,我们先看下它提供的原子操作方法,
getAndAddInt方法就是通过调用cas操作+自旋的方式保证线程安全
package sun.misc;
public final class Unsafe {
//通过这个静态属性就可以获取到这个类的对象,只能通过反射来获取
private static final Unsafe theUnsafe;
//这三个方法是unsafe类提供的cas操作,是通过本地方法实现的
//cas的意思是比较并设置值,参数一般会有一个期望旧值,将要设置的目标值,
// 方法执行时会先判断变量的原始值是否和期望值一样,如果是就更新成目标值然后返回true,
//如果变量的原始值和期望值不一样就返回false,
//这个方法是通过本地方法(最终通过cpu指令)保证了上边这个比较并设置值的过程是原子操作
/**
* obj: 要修改那个对象的属性
* offset: 对象属性相对于对象的内存偏移量
* expect: 期望的旧值
* newVal: 要设置成的目标值
*/
public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object newVal);
public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int newVal);
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long newVal);
//这是自增的方法,注意这个方法是先返回原来的值再自增的,类似i++这样的操作
public final int getAndAddInt(Object obj, long offset, int step) {
int var5;//这是对象属性的原始值
do {
//调用本类中直接操作内存的方法来获取对象obj的内存偏移量是offset的属性的最新值,
//针对原子整数AtomicInteger就是获取value属性的最新值
var5 = this.getIntVolatile(obj, offset);
//这块就是调用cas方法设置对象属性的值,有可能成功或者失败,
//如果失败就再次循环重新获取旧值进行更新的操作,直到更新成功退出循环
} while(!this.compareAndSwapInt(obj, offset, var5, var5 + step));
return var5;
}
}
总结下getAndAddInt方法就是通过这样的cas操作+自旋的方式保证了外界调用的一次自增/自减操作一定是在最新值对象属性值的基础上进行的,这样就保证了这次操作的原子性,保证线程安全。
四、尝试使用Unsafe保证线程安全
这一节我们尝试下直接使用Unsaft类中的cas操作来保证自增/自减操作的原子性。
首先创建一个计数器类
public class Counter {
// volatile保证多线程可见行
public volatile int count;
public Counter(int count) {
this.count = count;
}
}
测试类
@Slf4j
public class ThreadTest2 {
//使用Counter类作为计数器
static Counter counter = new Counter(0);
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
//利用反射获取Unsafe对象
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);//静态变量获取时传null
//获取Counter中count属性的内存偏移量
long offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));
for (int k = 0; k < 100; k++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//自己实现cas+自旋的操作
boolean res=false;
while (!res) {
//获取旧值
int old = unsafe.getIntVolatile(counter,offset);
//cas设置新值,得到cas操作结果
res = unsafe.compareAndSwapInt(counter,offset,old,old+1);
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//自己实现cas+自旋的操作
boolean res=false;
while (!res) {
//获取旧值
int old = unsafe.getIntVolatile(counter,offset);
//cas设置新值,得到cas操作结果
res = unsafe.compareAndSwapInt(counter,offset,old,old-1);
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
log.info("res:{}", counter.count);
}
}
}
这段代码中关键是使用cas操作实现自增和自减