java 多线程跳表和无锁
Skip List是 William Pugh 在1989年创建出来的(又见一个位神牛), 主要的目的就像他描述的那样,是用来替代平衡树的。跳表是一种随机性的数据结构,相对于平衡树来说,跳表更加的简单,能一口气实现红黑树,AVL这样的平衡树的人,还是太少了,而且内部确实复杂,调试, 用起来太麻烦。 同样跳表还可以做到平衡树那样的查找时间,特别是在并发的场景下面,由于红黑树的插入或者删除会做rebalance这样操作,那么影响的数据就会变多,锁的粒度就变大。但是跳表的插入或者删除操作影响的数据会很小,锁的粒度就会小,这样在大数据量的情况下,跳表的性能自然就会比红黑树要好。
如图
现在我们查询10可以很快查到出8-12中,提高查询效率。
简单介绍了跳表设计思想和应用,以及在高并发的情况下,优于平衡树的原因,所以跳表在并发计算领域出现了,redis, leveldb都用应用,并且在分布式里面可以利用跳表的无锁特性来实现优先级队列。跳表其实也是一种利用空间换取时间的方法。
下面说一下java 为我们实现的跳表,ConcurrentSkipListMap
在介绍这个类之前,介绍一下无锁,cas比较交换
之前我们多线程时需要加锁,但是加锁复杂和执行效率低。
这时我们可以这样考虑一个在多线程下,出错是小概率事件,这就是乐观,反之,就是悲观。
cas(v,e,n) v是要更新的变量 e预测值 N表示新值
v==e的是将n赋给v。在硬件层面,现在的处理器都支持原子化的CAS指令。在jdk5.0 以后,虚拟机使用这个指令实现并发操作和并发数据结构。
无锁的线程安全的整数 AtomicInteger,讲了这么多理论,举个栗子。
import java.util.concurrent.atomic.AtomicInteger;
public class IntTest {
static AtomicInteger atomicInteger=new AtomicInteger();
public static class AddThread implements Runnable{
@Override
public void run() {
for(int k=0;k<10000;k++){
atomicInteger.incrementAndGet();
}
}
}
public static void main(String args[]) throws InterruptedException {
Thread []tt=new Thread[10];
for(int i=0;i<10;i++){
tt[i]=new Thread(new AddThread());
}
for(int i=0;i<10;i++){
tt[i].start();
}
for(int i=0;i<10;i++){
tt[i].join();
}
System.out.println(atomicInteger);
}
}
输出为10000,所以实现了线程安全的Integer。
incrementAndGet在jdk 1.7下的源码
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
可以看出就是在不断尝试中,乐观。
继续看源码发现,出现unsafe
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Unsafe 封装一些类型指针的操作,但是jdk的开发人员不希望我们进行调用。
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass(2);
if(var0.getClassLoader() != null) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
我们直接调用返回,报错,抛出异常,原理双亲委派机制。