在延迟锁定时,在持有特定监视器的同时修改对象的易失性布尔字段有什么用?
What is the deal with modifying the volatile boolean field of an object, while holding the specific monitor, when lazy locking?
我实现了一个惰性锁定列表,它支持以下方法:boolean add(T item)、boolean remove(T item)、boolean contains(T item)。
例如添加方法:
@Override
public boolean add(T item) {
int key = item.hashCode();
while(true){
Node pred = head;
Node curr = pred.next;
while(curr.key < key) { pred = curr; curr = curr.next; }
pred.Lock.lock();
curr.Lock.lock();
try{
if(!pred.marked && !curr.marked && pred.next == curr){
if(curr.key == key){ return false; }
else{ Node insertMe = new Node(item); insertMe.next = curr; pred.next = insertMe; return true; }
}
} finally{ pred.Lock.unlock(); curr.Lock.unlock(); }
}
}
其中 Node 对象有一个可变的布尔字段 "marked",默认设置为 false。节点的锁是可重入的。标记为 true 的节点被认为已被 remove 方法删除。然而,当特定对象的监视器无论如何都由特定线程持有时,为什么这甚至是相关的?换句话说,它是否会在任何时候发生,一个线程正在获取一个对象的锁,然后看到该节点被标记为已删除?
编辑:
contains 方法显然根本不锁定,但是将节点标记为已删除的目的只是为了能够进行 contains 检查吗?因此是否有必要将标记的字段声明为 volatile?
包含:
@Override
public boolean contains(T item) {
int key = item.hashCode();
while(true){
Node curr = head;
while(curr.key < key) { curr = curr.next; }
if(curr.key == key && !curr.marked) return true;
else return false;
}
}
删除:
@Override
public boolean remove(T item) {
int key = item.hashCode();
while(true){
Node pred = head;
Node curr = pred.next;
while(curr.key < key) { pred = curr; curr = curr.next; }
pred.Lock.lock();
curr.Lock.lock();
try{
if(!pred.marked && !curr.marked && pred.next == curr){
if(curr.key != key) { return false; }
else{
curr.marked = true;
pred.next = curr.next;
return true;
}
}
} finally{ pred.Lock.unlock(); curr.Lock.unlock(); }
}
}
在您的情况下,marked
是 contains 方法所必需的,而不是 add 方法所必需的。我的猜测是添加函数中的 marked
-check 绝对没有任何作用。
marked
字段绝对必须是可变的,因为 JIT 保留重新排序代码的权利(如果这样做有好处)。唯一的保证是在更改 volatile 字段之前完全执行了对 volatile 字段的任何操作之前的任何代码(happens-before 关系)。
所以在上面的代码中,如果该字段不是易失性的,JIT 可以决定在调用 'contains' 的线程中制作 marked
的线程本地副本,然后检查该副本marked
的本地版本,但当时可能已经过时。在这种情况下 运行 contains
和 remove
同时出现可能会导致竞争条件。
作为旁注,应该提到 Concurrent 包中的集合可以更优雅地解决所有这些问题(并且没有错误),因此在所有情况下都应该是首选。如果你复制标准库中已经存在的 class,你通常会更糟糕地实现它。
我实现了一个惰性锁定列表,它支持以下方法:boolean add(T item)、boolean remove(T item)、boolean contains(T item)。
例如添加方法:
@Override
public boolean add(T item) {
int key = item.hashCode();
while(true){
Node pred = head;
Node curr = pred.next;
while(curr.key < key) { pred = curr; curr = curr.next; }
pred.Lock.lock();
curr.Lock.lock();
try{
if(!pred.marked && !curr.marked && pred.next == curr){
if(curr.key == key){ return false; }
else{ Node insertMe = new Node(item); insertMe.next = curr; pred.next = insertMe; return true; }
}
} finally{ pred.Lock.unlock(); curr.Lock.unlock(); }
}
}
其中 Node 对象有一个可变的布尔字段 "marked",默认设置为 false。节点的锁是可重入的。标记为 true 的节点被认为已被 remove 方法删除。然而,当特定对象的监视器无论如何都由特定线程持有时,为什么这甚至是相关的?换句话说,它是否会在任何时候发生,一个线程正在获取一个对象的锁,然后看到该节点被标记为已删除?
编辑: contains 方法显然根本不锁定,但是将节点标记为已删除的目的只是为了能够进行 contains 检查吗?因此是否有必要将标记的字段声明为 volatile?
包含:
@Override
public boolean contains(T item) {
int key = item.hashCode();
while(true){
Node curr = head;
while(curr.key < key) { curr = curr.next; }
if(curr.key == key && !curr.marked) return true;
else return false;
}
}
删除:
@Override
public boolean remove(T item) {
int key = item.hashCode();
while(true){
Node pred = head;
Node curr = pred.next;
while(curr.key < key) { pred = curr; curr = curr.next; }
pred.Lock.lock();
curr.Lock.lock();
try{
if(!pred.marked && !curr.marked && pred.next == curr){
if(curr.key != key) { return false; }
else{
curr.marked = true;
pred.next = curr.next;
return true;
}
}
} finally{ pred.Lock.unlock(); curr.Lock.unlock(); }
}
}
在您的情况下,marked
是 contains 方法所必需的,而不是 add 方法所必需的。我的猜测是添加函数中的 marked
-check 绝对没有任何作用。
marked
字段绝对必须是可变的,因为 JIT 保留重新排序代码的权利(如果这样做有好处)。唯一的保证是在更改 volatile 字段之前完全执行了对 volatile 字段的任何操作之前的任何代码(happens-before 关系)。
所以在上面的代码中,如果该字段不是易失性的,JIT 可以决定在调用 'contains' 的线程中制作 marked
的线程本地副本,然后检查该副本marked
的本地版本,但当时可能已经过时。在这种情况下 运行 contains
和 remove
同时出现可能会导致竞争条件。
作为旁注,应该提到 Concurrent 包中的集合可以更优雅地解决所有这些问题(并且没有错误),因此在所有情况下都应该是首选。如果你复制标准库中已经存在的 class,你通常会更糟糕地实现它。