在java中,涉及到共享数据,一般同学会使用同步方法或者同步代码块,高级一点的同学会使用Lock,不管使用哪一种,基本上是对共享数据进行加锁访问。今天介绍Lock-Free,开始听到这个的时候,感觉无锁编程好爽啊。首先无锁,是在代码层面上看不到同步代码,它是依赖于硬件来实现的,下面就谈谈这个无锁编程。
原子类AtomicInteger,对于大家应该是不会陌生的,那么实现一个多线程自增加法的操作也不会太难,代码如下。
class LockFreeThread implements Runnable {
private AtomicInteger count;
private CountDownLatch latch;
public LockFreeThread(AtomicInteger count, CountDownLatch latch) {
this.count = count;
this.latch = latch;
}
@Override
public void run() {
for (int i = 0; i < 10000000; i++) {
count.incrementAndGet();
}
latch.countDown();
}
}
public class LockFreeDemo {
public static void main(String args[]) throws InterruptedException {
// 初始化count
AtomicInteger count = new AtomicInteger(0);
// 同步计数器
CountDownLatch latch = new CountDownLatch(5);
LockFreeThread t = new LockFreeThread(count, latch);
// 5线程同时自增相加
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
Thread t5 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
// 等待线程结束
latch.await();
System.out.println("count = " + count);
}
}
在代码里,是没有发现使用同步操作,但它能正确的运行出我们期望的结果。它里面主要使用了CAS(Compare-And-Swap)来实现的,CAS在AQS框架中使用得比较多,现在看看它是如何来实现的吧。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
它里面有一个循环,相当于是说如果没有return值,它就会一直循环,里面又有一个compareAndSet方法,compareAndSet方法里面有一个compareAndSwapInt方法,把它理解了就ok了。那就分析它的参数了,明白了含义,就不难理解它的用法和意义。
this:表示调用的对象,在这个示例中就是count了;
valueOffset:其实它是 count value的内存偏移地址,this+valueOffset其实就是能找到value的内存地址,找到了变量的内存地址,拿到它的值还不是分分钟钟的事;
expect:就是预期的值;
update:就是最后需更新的值。
所以它的简单逻辑就是这样的:
if (this+valueOffset == except){
value = update;
}
所以说无锁编程并不是真正意义上的无锁,其实底层已经帮我们写好了,仅仅是在代码层面不用考虑而已。