如何从地址中窃取2个MSB以执行原子操作?我想做一个单词CAS
示例
public class Node
{
long key;
long value;
Node lchild; // format is flag1,flag2,address
Node rchild; // format is flag1,flag2,address
}
public void createNode()
{
Node n1 = new Node(); //this should create a node with format 0,0,address1
}
public void setFlag1(Node n1)
{
Now the new address should be in format 1,0,address1
}
public void setFlag2(Node n1)
{
Now the new address should be in format 0,1,address1
}
如果我只需要一个额外的标志,就可以使用AtomicReference
。可以使用AtomicStampedReference
,但它没有效率,因为它创建了一个包含时间戳和引用的额外框。
中讨论了C中的一个类似问题从指针中窃取位
您可能可以使用sun.msic.Unsafe 来实现这一点
- http://www.docjar.com/docs/api/sun/misc/Unsafe.html
除其他外,它有许多compareAndSwap
方法,可以处理任何对象中的任意二进制数据。
话虽如此,如果你正在实现一个二进制搜索树,我建议你看看不可变的持久数据结构。这些优点包括:
- 根据定义,由于不变性,它们是线程安全的
- 它们不需要锁
- 一旦你开始做更复杂的事情(例如子树的快照),进行结构共享的能力将是一个巨大的性能胜利——基本上你就不需要进行防御拷贝了
如果不实现自己的JVM,这是不可能的,JVM支持这种操作并正确处理标志位,例如在执行GC时(此时需要识别所有引用,移动收集器需要更改它们)。即便如此,这也违背了Java的设计原则,Java的设计原理不包括显式的去引用或指针算法(我会计算引用中的变化位,并将它们屏蔽以进行去引用)。
相反,我建议您创建一个新的组合Edge类型的标志和节点:
public class Node {
long key;
long value;
Child lchild; // child = node reference + flags
Child rchild;
}
// this is the edge type which should also be used to reference the root node with flags
public class Child {
Node child;
BitSet flags;
public Child(Node node) {
this.child = node;
this.flags = new BitSet(2); // we initialize the flags with 00
}
public void setFlag(int index) {
this.flags.set(index); // index would be 0 or 1 here for the two flags
}
public boolean isFlagSet(int index) {
return this.flags.get(index); // index would be 0 or 1 here for the two flags
}
}
复制Maurice Herlihy和Nir Shavit的《多处理器编程的艺术》第215页的摘录:
如Pragma 9.8.1中详细描述的,AtomicMarkableReference对象封装了对T类型对象的引用和布尔标记。这些字段可以一起原子更新或者单独地。我们使每个节点的下一个字段AtomicMarkableReference。线程A通过逻辑删除currA在节点的下一个字段中设置标记位,并共享物理与执行add()或remove()的其他线程一起删除:按每个线程线程遍历列表,它通过物理方式清理列表移除(使用compareAndSet())它遇到的任何标记节点。在里面换句话说,执行add()和remove()的线程不会遍历标记的节点,在继续之前将其删除。容器()方法与LazyList算法保持相同,遍历所有节点(无论是否标记),并测试项目是否在根据其键和标记列出。
我能给你的在Java中工作的最好建议是对数组索引而不是地址进行处理,因为Java不公开地址。
要从可用于对象引用变量的位中提取位,需要创建自己的JVM。
您首先必须确保这些位没有被实际使用(例如,当JVM总是在16字节的边界上对齐对象时,通过在引用中取低阶位)。但是有些JVM使用32位引用的所有位。
接下来,您必须在每次加载引用时注入代码,以便在访问相关对象之前清除这些位。
然后,您必须对垃圾收集器执行同样的操作。
您不允许从引用中窃取位,但有一种方法可以绕过它:您可以使用AtomicStampedReference,它允许您进行比较和交换,以原子地更新引用和整数。可以将整数用作状态,也可以根据需要从整数的MSB中窃取位。您可以在java中对整数进行位运算,这非常酷:
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html
我最终编写了一个java程序,从AtomicStampedReference的整数中窃取了2位,并将它们用作状态位,并使用整数的剩余30位作为计数器来防止ABA问题。