-
Notifications
You must be signed in to change notification settings - Fork 0
/
LazySkipList.java
337 lines (313 loc) · 11.9 KB
/
LazySkipList.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ThreadLocalRandom;
import java.util.Random;
public final class LazySkipList {
static final int MAX_LEVEL = 32;
static final double Prob = 0.5;
final Node head = new Node(Integer.MIN_VALUE); // set head to the smallest integer
final Node tail = new Node(Integer.MAX_VALUE); // set tail to the biggest integer
public LazySkipList() {
for (int i = 0; i < head.next.length; i++) {
head.next[i] = tail; // initialize all layers with only the head and the tail
}
}
// generate a random level number that provides a balancing property
// Top levels are chosen so that the expected number of nodes in each level’s list decreases exponentially
private int randomLevel() {
int level = 1;
for (int i = 0; i < MAX_LEVEL; i++) {
float f = ThreadLocalRandom.current().nextFloat();
if (f <= Prob) {
level++;
}
else {
return level;
}
}
return level;
}
int find(int x, Node[] preds, Node[] succs) {
int key = x;
// will return -1 if not found in any layer
int layerFound = -1;
// start traversal from the head sentinel
Node pred = head;
for (int level = MAX_LEVEL; level >= 0; level--) {
Node curr = pred.next[level];
// compare key with curr.key until hit tail, i.e. a MAX_VALUE
while (key > curr.key) {
pred = curr;
curr = pred.next[level];
}
if (layerFound == -1 && key == curr.key) {
layerFound = level;
}
preds[level] = pred;
succs[level] = curr;
}
return layerFound;
}
boolean add(int x) {
// initialize pred and succ arrays
Node[] preds = (Node[]) new Node[MAX_LEVEL + 1];
Node[] succs = (Node[]) new Node[MAX_LEVEL + 1];
// keep trying until returned
while (true) {
int layerFound = find(x, preds, succs);
// Case 1 - element found in the list
if (layerFound != -1) {
// node would be included as the entry in succs on the layer found
Node nodeFound = succs[layerFound];
if (nodeFound.marked != true) {
while (nodeFound.fullyLinked != true) {
// wait until it's phisically linked to the skiplist
}
// unsuccessful add
return false;
}
continue;
}
// Case 2 - not found in the list
int highestLocked = -1;
int topLevel = randomLevel();
try {
Node pred, succ;
boolean valid = true;
// validate and lock every predecessor in the path, bottom-up
for (int level = 0; valid && (level <= topLevel); level++) {
pred = preds[level];
succ = succs[level];
pred.lock.lock();
highestLocked = level;
valid = !pred.marked && !succ.marked && pred.next[level] == succ;
}
if (!valid) {
continue;
}
// create a new node with a random top-level
Node newNode = new Node(x, topLevel);
// Question: why using two seperate iterations?
for (int level = 0; level <= topLevel; level++) {
newNode.next[level] = succs[level];
}
for (int level = 0; level <= topLevel; level++) {
preds[level].next[level] = newNode;
}
// successful add
newNode.fullyLinked = true;
return true;
} finally {
// release locks
for (int level = 0; level <= highestLocked; level++) {
preds[level].lock.unlock();
}
}
}
}
boolean remove(int x) {
Node victim = null;
boolean isMarked = false;
int topLevel = -1;
// initialize pred and succ arrays
Node[] preds = (Node[]) new Node[MAX_LEVEL + 1];
Node[] succs = (Node[]) new Node[MAX_LEVEL + 1];
while (true) {
// use find to check if node is in list
int layerFound = find(x, preds, succs);
if (layerFound != -1) {
victim = succs[layerFound];
}
if (isMarked ||
(layerFound != -1 && // found in list
victim.fullyLinked && // fully linked
victim.topLevel == layerFound && // at its top level
!victim.marked) // not marked already
) {
// ready to delete
if (!isMarked) { // not marked yet
// Logical delete
topLevel = victim.topLevel;
victim.lock.lock();
// validate after lock that it's still not marked
if (victim.marked) {
victim.lock.unlock();
// already deleted, unsuccessful delete
return false;
}
// successful delete
victim.marked = true;
isMarked = true;
}
int highestLocked = -1;
// Physical delete
try {
Node pred;
boolean valid = true;
// validate and lock every predecessor in the path, bottom-up
for (int level = 0; valid && (level <= topLevel); level++) {
pred = preds[level];
pred.lock.lock();
highestLocked = level;
valid = !pred.marked && pred.next[level] == victim;
}
if (!valid) {
continue;
}
// physically remove link to victim, top-down
for (int level = topLevel; level >= 0; level--) {
preds[level].next[level] = victim.next[level];
}
victim.lock.unlock();
// successful physical delete
return true;
} finally {
// release locks
for (int level = 0; level <= highestLocked; level++) {
preds[level].unlock();
}
}
} else {
// find did not find a matching node, or not valid (see above conditions)
// unsuccessful delete
return false;
}
}
}
boolean contains(int x) {
Node[] preds = (Node[]) new Node[MAX_LEVEL + 1];
Node[] succs = (Node[]) new Node[MAX_LEVEL + 1];
int layerFound = find(x, preds, succs);
// only return true if found in list, fully linked and not marked
return layerFound != -1 && succs[layerFound].fullyLinked && !succs[layerFound].marked;
}
private static final class Node {
final Lock lock = new ReentrantLock();
final int item;
final int key;
final Node[] next;
// only a unmarked, fullyLinked node is considered a part of the list
volatile boolean marked = false;
volatile boolean fullyLinked = false;
private int topLevel;
// sentinel node constructor (head and tail)
public Node(int key) {
this.item = key;
this.key = key;
this.next = new Node[MAX_LEVEL + 1];
this.topLevel = MAX_LEVEL;
}
public Node(int x, int height) {
this.item = x;
this.key = x;
this.next = new Node[height + 1];
this.topLevel = height;
}
public void lock() {
lock.lock();
}
public void unlock() {
lock.unlock();
}
}
public static void main(String [] args) throws InterruptedException {
LazySkipList list = new LazySkipList();
ThreadLocalRandom rand = ThreadLocalRandom.current();
long start, end;
int n = 20000;
int num_threads = 200;
AddThread[] add_threads = new AddThread[num_threads];
ContainsThread[] contains_threads = new ContainsThread[num_threads];
RemoveThread[] remove_threads = new RemoveThread[num_threads];
int[][] number_lists = new int[num_threads][n];
for (int i = 0; i < num_threads; i++) {
for (int j = 0; j < n; j++) {
number_lists[i][j] = rand.nextInt(0,n);
}
}
// test add
start = System.currentTimeMillis();
for (int i = 0; i < num_threads; i++) {
add_threads[i] = new AddThread(list, number_lists[i]);
}
for (int i = 0; i < num_threads; i++) {
add_threads[i].start();
}
try {
for (int i = 0; i < num_threads; i++) {
add_threads[i].join();
}
} catch (Exception e) {}
end = System.currentTimeMillis();
System.out.println("Java concurrent add() " + (n * num_threads) + " nodes with " + num_threads + " threads, time: " + (end - start) / 1000.0 + " s");
// test contains
start = System.currentTimeMillis();
for (int i = 0; i < num_threads; i++) {
contains_threads[i] = new ContainsThread(list, number_lists[i]);
}
for (int i = 0; i < num_threads; i++) {
contains_threads[i].start();
}
try {
for (int i = 0; i < num_threads; i++) {
contains_threads[i].join();
}
} catch (Exception e) {}
end = System.currentTimeMillis();
System.out.println("Java concurrent contains() " + (n * num_threads) + " nodes with " + num_threads + " threads, time: " + (end - start) / 1000.0 + " s");
// test remove
start = System.currentTimeMillis();
for (int i = 0; i < num_threads; i++) {
remove_threads[i] = new RemoveThread(list, number_lists[i]);
}
for (int i = 0; i < num_threads; i++) {
remove_threads[i].start();
}
try {
for (int i = 0; i < num_threads; i++) {
remove_threads[i].join();
}
} catch (Exception e) {}
end = System.currentTimeMillis();
System.out.println("Java concurrent remove() " + (n * num_threads) + " nodes with " + num_threads + " threads, time: " + (end - start) / 1000.0 + " s");
}
}
class AddThread extends Thread {
LazySkipList list;
int[] nodes;
public AddThread(LazySkipList list, int[] nodes) {
this.list = list;
this.nodes = nodes;
}
public void run() {
for (int i = 0; i < nodes.length; i++) {
list.add(nodes[i]);
}
}
}
class ContainsThread extends Thread {
LazySkipList list;
int[] nodes;
public ContainsThread(LazySkipList list, int[] nodes) {
this.list = list;
this.nodes = nodes;
}
public void run() {
for (int i = 0; i < nodes.length; i++) {
list.contains(nodes[i]);
}
}
}
class RemoveThread extends Thread {
LazySkipList list;
int[] nodes;
public RemoveThread(LazySkipList list, int[] nodes) {
this.list = list;
this.nodes = nodes;
}
public void run() {
for (int i = 0; i < nodes.length; i++) {
list.remove(nodes[i]);
}
}
}