很久没刷leetcode,今天刷leetcode时,遇到了跳表题目,传送门:中文版leetcode跳表题目,于是学习了下ConcurrentSkipListMap同时也是学习并发,关于ConcurrentSkipListMap是无锁的,跳表平时基本没用过,会讲解少许源码,但是不会细讲,因为关于这个源码讲解百度太多了,讲解之前,先把我抄袭的跳表代码贴出来,去除了并发操作,但是放入leetcode执行时,超时了...
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicLong;
public class SkipList {
private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL;
private static final AtomicLong seeder = new AtomicLong(initialSeed());
private static long SEED;
private static long SECONDARY;
private static final Object BASE_HEADER = new Object();
private HeadIndex head;
final Comparator super K> comparator ;
public SkipList(){
this(null);
}
public SkipList(Comparator super K> comparator) {
this.comparator = comparator;
initialize();
}
private void initialize() {
head = new HeadIndex(new Node(null, BASE_HEADER, null),
null, null, 1);
}
public V get(Object key) {
return doGet(key);
}
private V doGet(Object key) {
if (key == null)
throw new NullPointerException();
Comparator super K> cmp = comparator;
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
int c;
if (n == null)
break;
Node f = n.next;
Object v = n.value;
if ((c = cpr(cmp, key, n.key)) == 0) {
V vv = (V)v;
return vv;
}
if (c < 0)
break;
b = n;
n = f;
}
return null;
}
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
return doPut(key, value);
}
private V doPut(K key, V value) {
Node z; // added node
if (key == null)
throw new NullPointerException();
Comparator super K> cmp = comparator;
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
if (n != null) {
int c;
Node f = n.next;
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
if (c == 0) {
n.value = value;
V vv = (V)n.value;
return vv;
}
}
z = new Node(key, value, n);
b.next = z;
break ;
}
int rnd = nextSecondarySeed();
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
int level = 1, max;
while (((rnd >>>= 1) & 1) != 0)
++level;
Index idx = null;
HeadIndex h = head;
if (level <= (max = h.level)) {
for (int i = 1; i <= level; ++i)
idx = new Index(z, idx, null);
}
else { // try to grow by one level
level = max + 1; // hold in array and later pick the one to use
Index[] idxs = (Index[])new Index,?>[level+1];
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index(z, idx, null);
for (;;) {
h = head;
int oldLevel = h.level;
if (level <= oldLevel) // lost race to add level
break;
HeadIndex newh = h;
Node oldbase = h.node;
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex(oldbase, newh, idxs[j], j);
h = newh;
idx = idxs[level = oldLevel];
break;
}
}
// find insertion points and splice in
int insertionLevel = level;
int j = h.level;
for (Index q = h, r = q.right, t = idx;;) {
if (q == null || t == null)
break ;
if (r != null) {
Node n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
if (j == insertionLevel) {
q.right = t;
t.right = r;
if (--insertionLevel == 0)
break ;
}
if (--j >= insertionLevel && j < level)
t = t.down;
q = q.down;
r = q.right;
}
}
return null;
}
public V remove(Object key) {
return doRemove(key, null);
}
final V doRemove(Object key, Object value) {
if (key == null)
throw new NullPointerException();
Comparator super K> cmp = comparator;
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
int c;
if (n == null)
break ;
Node f = n.next;
Object v = n.value;
if ((c = cpr(cmp, key, n.key)) < 0)
break ;
if (c > 0) {
b = n;
n = f;
continue;
}
if (value != null && !value.equals(v))
break ;
b.next = f;
n.value = null;
findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
V vv = (V)v;
return vv;
}
return null;
}
private void tryReduceLevel() {
HeadIndex h = head;
HeadIndex d;
HeadIndex e;
if (h.level > 3 &&
(d = (HeadIndex)h.down) != null &&
(e = (HeadIndex)d.down) != null &&
e.right == null &&
d.right == null &&
h.right == null )
h = d;
}
private Node findPredecessor(Object key, Comparator super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (Index q = head, r = q.right, d;;) {
if (r != null) {
Node n = r.node;
K k = n.key;
if (n.value == null) {
q.right = r.right;
continue;
}
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
}
if ((d = q.down) == null)
return q.node;
q = d;
r = d.right;
}
}
static final int cpr(Comparator c, Object x, Object y) {
return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y);
}
private static long initialSeed() {
String pp = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
"java.util.secureRandomSeed"));
if (pp != null && pp.equalsIgnoreCase("true")) {
byte[] seedBytes = java.security.SecureRandom.getSeed(8);
long s = (long)(seedBytes[0]) & 0xffL;
for (int i = 1; i < 8; ++i)
s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
return s;
}
return (mix64(System.currentTimeMillis()) ^
mix64(System.nanoTime()));
}
private static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = (int)SECONDARY) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else {
localInit();
if ((r = (int)SEED) == 0)
r = 1; // avoid zero
}
SECONDARY = r;
return r;
}
private static final void localInit() {
SEED = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
}
private static long mix64(long z) {
z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L;
return z ^ (z >>> 33);
}
static final class Node {
final K key;
Object value;
Node next;
Node(K key, Object value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
}
static class Index {
final Node node;
final Index down;
Index right;
Index(Node node, Index down, Index right) {
this.node = node;
this.down = down;
this.right = right;
}
}
static final class HeadIndex extends Index {
final int level;
HeadIndex(Node node, Index down, Index right, int level) {
super(node, down, right);
this.level = level;
}
}
}
但leetcode的key值是int没有value值,所以整理leetcode的代码如下:
import java.util.Comparator;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
public class Skiplist {
private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL;
private static final AtomicLong seeder = new AtomicLong(initialSeed());
private static long SEED;
private static long SECONDARY;
private HeadIndex head;
final Comparator comparator = null;
private Random random = new Random();
public Skiplist() {
head = new HeadIndex(new Node(null, null),
null, null, 1);
}
public boolean search(int target) {
return doGet(target);
}
private boolean doGet(int key) {
Comparator cmp = comparator;
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
int c;
if (n == null)
break;
Node f = n.next;
if ((c = cpr(cmp, key, n.key)) == 0) {
return true;
}
if (c < 0)
break;
b = n;
n = f;
}
return false;
}
public void add(int num) {
doPut(num);
}
private void doPut(int key) {
Node z; // added node
Comparator cmp = comparator;
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
if (n != null) {
int c;
Node f = n.next;
if ((c = cpr(cmp, key, n.key)) > 0) {
b = n;
n = f;
continue;
}
if (c == 0) {
return ;
}
}
z = new Node(key, n);
b.next = z;
break ;
}
int rnd = nextSecondarySeed();
// int rnd = random.nextInt();
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
int level = 1, max;
while (((rnd >>>= 1) & 1) != 0)
++level;
Index idx = null;
HeadIndex h = head;
if (level <= (max = h.level)) {
for (int i = 1; i <= level; ++i)
idx = new Index(z, idx, null);
}
else { // try to grow by one level
level = max + 1; // hold in array and later pick the one to use
Index[] idxs = (Index[])new Index[level+1];
for (int i = 1; i <= level; ++i)
idxs[i] = idx = new Index(z, idx, null);
h = head;
int oldLevel = h.level;
if (level > oldLevel) {
HeadIndex newh = h;
Node oldbase = h.node;
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex(oldbase, newh, idxs[j], j);
h = newh;
idx = idxs[level = oldLevel];
}
}
// find insertion points and splice in
int insertionLevel = level;
int j = h.level;
for (Index q = h, r = q.right, t = idx;;) {
if (q == null || t == null)
break ;
if (r != null) {
Node n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
if (j == insertionLevel) {
q.right = t;
t.right = r;
if (--insertionLevel == 0)
break ;
}
if (--j >= insertionLevel && j < level)
t = t.down;
q = q.down;
r = q.right;
}
}
}
public boolean erase(int num) {
return doRemove(num);
}
final boolean doRemove(int key) {
Comparator cmp = comparator;
for (Node b = findPredecessor(key, cmp), n = b.next;;) {
int c;
if (n == null)
break ;
Node f = n.next;
if ((c = cpr(cmp, key, n.key)) < 0)
break ;
if (c > 0) {
b = n;
n = f;
continue;
}
b.next = f;
n.isDel = true;
// findPredecessor(key, cmp); // clean index
if (head.right == null)
tryReduceLevel();
return true;
}
return false;
}
private void tryReduceLevel() {
HeadIndex h = head;
HeadIndex d;
HeadIndex e;
if (h.level > 3 &&
(d = (HeadIndex)h.down) != null &&
(e = (HeadIndex)d.down) != null &&
e.right == null &&
d.right == null &&
h.right == null )
h = d;
}
private Node findPredecessor(int key, Comparator cmp) {
for (Index q = head, r = q.right, d;;) {
if (r != null) {
Node n = r.node;
Integer k = n.key;
if (n.isDel) {
q.right = r.right;
continue;
}
if (cpr(cmp, key, k) > 0) {
q = r;
r = r.right;
continue;
}
}
if ((d = q.down) == null)
return q.node;
q = d;
r = d.right;
}
}
static final class Node {
final Integer key;
Node next;
boolean isDel;
Node(Integer key, Node next) {
this.key = key;
this.next = next;
}
}
static class Index {
final Node node;
final Index down;
Index right;
Index(Node node, Index down, Index right) {
this.node = node;
this.down = down;
this.right = right;
}
}
static final class HeadIndex extends Index {
final int level;
HeadIndex(Node node, Index down, Index right, int level) {
super(node, down, right);
this.level = level;
}
}
static final int cpr(Comparator c, Object x, Object y) {
return (c != null) ? c.compare(x, y) : ((Comparable)x).compareTo(y);
}
private static long initialSeed() {
// String pp = java.security.AccessController.doPrivileged(
// new sun.security.action.GetPropertyAction(
// "java.util.secureRandomSeed"));
// if (pp != null && pp.equalsIgnoreCase("true")) {
// byte[] seedBytes = java.security.SecureRandom.getSeed(8);
// long s = (long)(seedBytes[0]) & 0xffL;
// for (int i = 1; i < 8; ++i)
// s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
// return s;
// }
return (mix64(System.currentTimeMillis()) ^
mix64(System.nanoTime()));
}
private static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = (int)SECONDARY) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else {
localInit();
if ((r = (int)SEED) == 0)
r = 1; // avoid zero
}
SECONDARY = r;
return r;
}
private static final void localInit() {
SEED = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
}
private static long mix64(long z) {
z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L;
return z ^ (z >>> 33);
}
}
这个代码最终超时了,并非报错,超时的测试用例为:
["Skiplist","add","add","add","add","add","add","add","add","add","erase","search","add","erase","erase","erase","add","search","search","search","erase","search","add","add","add","erase","search","add","search","erase","search","search","erase","erase","add","erase","search","erase","erase","search","add","add","erase","erase","erase","add","erase","add","erase","erase","add","add","add","search","search","add","erase","search","add","add","search","add","search","erase","erase","search","search","erase","search","add","erase","search","erase","search","erase","erase","search","search","add","add","add","add","search","search","search","search","search","search","search","search","search"]
[[],[16],[5],[14],[13],[0],[3],[12],[9],[12],[3],[6],[7],[0],[1],[10],[5],[12],[7],[16],[7],[0],[9],[16],[3],[2],[17],[2],[17],[0],[9],[14],[1],[6],[1],[16],[9],[10],[9],[2],[3],[16],[15],[12],[7],[4],[3],[2],[1],[14],[13],[12],[3],[6],[17],[2],[3],[14],[11],[0],[13],[2],[1],[10],[17],[0],[5],[8],[9],[8],[11],[10],[11],[10],[9],[8],[15],[14],[1],[6],[17],[16],[13],[4],[5],[4],[17],[16],[7],[14],[1]]
然后我去看了题解,发现找出了两个答案一个是数组实现,一个双链表实现,数组的实现占用了大量的空间。Doug Lea也说过为什么不用数组实现,1)基于数组的实现似乎遇到了更多的复杂性和开销2)我们可以为遍历索引列表使用比基础列表更便宜的算法。ConcurrentSkipListMap注释上有写到在123行。双链表的实现和ConcurrentSkipListMap的实现思想大致相同,双链表代码的随机算法我不太认同因为每个层级的数据多少都是随机。我也不太想去找我的代码超时的原因了,第一:leetcode运行机制我不清楚,他们的超时是怎么计算的,第二:我不知道leetcode使用的那个jdk以及jdk版本,sun.security.action这个代码编译报错了。
上文又提到sun.security.action代码编译报错,也就是要讲解的随机算法,这是因为ConcurrentSkipListMap随机算法的实现用到了ThreadLocalRandom.nextSecondarySeed(),但nextSecondarySeed又不是public的,所以我把nextSecondarySeed实现提取了出来。ThreadLocalRandom.nextSecondarySeed和Random.next()随机算法实现不一样并且作者和实现的jdk版本也不一样,所以这两个算法我都进行了大量测试,发现最终实现的层级基本一致以及每个层级的数据多少都差不多,也就是说这两个方法的效果差不多。我在解释下随机算法中的几行代码:
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits
int level = 1, max;
while (((rnd >>>= 1) & 1) != 0)
++level;
...
}
第一行0x80000001二进制表示为1000 0000 0000 0000 0000 0000 0000 0001 然后与1做按位与,如果等于 0,说明这个随机数最高位和最低位都为 0,第三行((rnd >>>= 1) & 1) != 0,移除最后一位,低位连续为 1 的个数作为 level 的值。网上还有几种随机算法Random.next..然后与某个数比较,这种方法我没测试,毕竟Doug Lea实现了一种随机算法。
记录下ConcurrentSkipListMap并发问题,毕竟这个是重点,看源码时类的开头一般都有全文简介,这个一般是设计思想,一定要看注释,一定要看注释,一定要看注释。Unsafe类是并发包的基础。一般的用法为:
private volatile X x;
for(;;){
...
if(unsafe.compareAndSwapObject(...)){
... //得到控制权,做某些事 或者存储数据,然后退出循环
unsafe.putOrderedObject(x,...);
break;
}
...//没得到控制权,判断是否有多线程并发问题,然后继续循环
}
一般的运用如上述的代码并配合volatile,在获取数据时,首先将volatile赋值给临时变量,利用volatile的可见性获取最新数据,而如何处理并发问题,就要看具体的思路了。比如ConcurrentSkipListMap为什么用链表实现?为什么删除不直接删除数据,而是将value的值设成null?为什么会有marker节点?等等,一定要看注释,答案都在注释上。不详解源码,给一个链接这个文章翻译了注释并且也有一部分源码注解,传送门:https://www.jianshu.com/p/9268ae6cbe6f