Java のConcurrentSkipListMapに基づいて同時スキップ リスト マップを実装しています。違いは、リストに重複を許可することと、リストをインデックス可能にすることです(リストの N 番目の要素を見つけるには O(lg( n)) 時間、標準のスキップ リストのような O(n) 時間ではなく)。これらの変更は問題を引き起こしていません。
さらに、スキップ リストのキーは可変です。たとえば、リスト要素が整数 {0, 4, 7} の場合、リスト構造の変更を求めることなく、中間要素のキーを [0, 7] の任意の値に変更できます。キーが (-inf, -1] または [8, +inf) に変更された場合、要素は削除され、リストの順序を維持するために再度追加されます。これを削除とそれに続く O(lg(n)) 挿入として実装するのではなく、これを削除とそれに続く線形トラバーサルとそれに続く O(1) 挿入として実装します (O(1) - 99 の予想される実行時間で)ノードが隣接ノードと交換される時間の割合)。
完全に新しいノードを挿入することは (起動後に) まれであり、ノードを削除する (すぐに再追加することなく) ことは決してありません。ほとんどすべての操作は、i 番目のインデックスの要素を取得するための elementAt(i)、またはキーが変更された後にノードを交換するための操作です。
私が直面している問題は、キー変更クラスを実装する方法にあります。概念的には、次のようなことをしたいと思います
public class Node implements Runnable {
private int key;
private Node prev, next;
private BlockingQueue<Integer> queue;
public void update(int i) {
queue.offer(i);
}
public void run() {
while(true) {
int temp = queue.take();
temp += key;
if(prev.getKey() > temp) {
// remove node, update key to temp, perform backward linear traversal, and insert
} else if(next.getKey() < temp) {
// remove node, update key to temp, perform forward linear traveral, and insert
} else {
key = temp; // node doesn't change position
}
}
}
}
(insert
から呼び出されるサブメソッドはrun
、同じ場所に同時に挿入しようとする 2 つのノードの問題を処理するために CAS を使用します (ConcurrentSkipListMap
競合する挿入を処理する方法と同様) - 概念的には、これは最初のノードがノードをロックした場合と同じですただし、競合がない場合はオーバーヘッドが削減されます)。
このようにして、リストが常に順序どおりであることを確認できます (キーの更新が少し遅れても、最終的に更新が行われることは確実なので問題ありません。ただし、リストが順不同になると、事態は混乱する可能性があります)。問題は、このようにリストを実装すると、非常に多くのスレッドが生成され、1 つにつき 1 つNode
(リストに数千のノードがある) になることです。それらのほとんどは、特定の時点でブロックされますが、数千のスレッドが発生することを懸念しています。スレッドをブロックすると、オーバーヘッドが非常に高くなります。
もう 1 つのオプションは、update
メソッドを同期させ、Runnable
からインターフェイスを削除するNode
ことです。これにより、2 つのスレッドが で更新をエンキューしNode
、別のスレッドでこれらの更新を処理するのではなく、2 つのスレッドが交互にNode#update
メソッドを実行します。問題は、これがボトルネックになる可能性があることです。8 つの異なるスレッドがすべて一度に同じノードを更新することを決定した場合、キューの実装は問題なくスケーリングされますが、同期された実装は 8 つのスレッドのうち 7 つをブロックします (その後、6 つのスレッド、次に 5 つのスレッドをブロックするなど)。
したがって、私の質問は、スレッドの数を減らすことを除いて、キューの実装のようなものをどのように実装するか、または潜在的なボトルネックの問題がないことを除いて、同期された実装のようなものをどのように実装するかです。