我正在寻找间隔锁的实现。给定间隔(x, y)
,如果没有其他人正在获取包含点p
x <= p <= y
的任何间隔,则线程可以获取锁。
我目前的想法是维护一系列现有授权时间间隔(x1, y1, x2, y2, ..., xn, yn)
x1 < y1 < x2 < y2 < ... < xn < yn
,并检查(x, y)
是否与这些时间间隔重叠。
搜索需要O(logn)
次,这让我感到高兴。但是,当搜索返回有一些重叠时,lock
函数需要以某种方式有效地重试,直到它可以在其他人释放其间隔锁时获取锁。忙碌的等待或睡眠似乎不是一个好主意。
有没有办法有效地实施重试?
答案 0 :(得分:3)
正如@ c0der建议我做了一个只跟踪锁定间隔的实现。
我的代码暗示了一个Range
类......
equals()
和hashCode()
RangeLock
类目前仅实现阻塞锁定方法。解锁是通过返回的Unlocker
实例完成的。这是为了避免线程没有获得锁定,能够解锁给定的Range
。
public class RangeLock<T extends Comparable<? super T>> {
private final SortedSet<Range<T>> locked = new TreeSet<>(Comparator.comparing(Range::lower));
private final Object lock = new Object();
public Unlocker lock(Range<T> range) throws InterruptedException {
synchronized (lock) {
while (!available(range)) {
lock.wait();
}
locked.add(range);
return () -> {
synchronized (lock) {
locked.remove(range);
lock.notifyAll();
}
};
}
}
private boolean available(Range<T> range) {
SortedSet<Range<T>> tailSet = locked.tailSet(range);
SortedSet<Range<T>> headSet = locked.headSet(range);
return (tailSet.isEmpty() || !tailSet.first().overlaps(range)) && (headSet.isEmpty() || !headSet.last().overlaps(range));
}
public interface Unlocker {
void unlock();
}
}
答案 1 :(得分:1)
我认为这个问题主要是关于让线程等待和重试的有效方法 如何听取
中的变化现有授权间隔数组
并仅在更改后重试?
以下不应被视为正确的实现(我对线程的经验非常有限),但是对所提出的机制的演示:
Ranges.java 和 Range.java
//represents all ranges
//see also: https://stackoverflow.com/a/7721388/3992939
public class Ranges {
private List<Range> ranges = new ArrayList<>();
private PropertyChangeSupport rangeChangedProperty = new PropertyChangeSupport(this);
public Range getRange(int rangeStart, int rangeEnd) {
if(contains(rangeStart) || contains(rangeEnd)) {
return null;
}
Range range = new Range(rangeStart, rangeEnd);
range.addListener( (observable, oldValue, newValue) -> {
rangeChangedProperty.firePropertyChange("Range", "-" , "changed");
}
);
ranges.add(range);
return range;
}
private boolean contains(int number){
for(Range range : ranges) {
if(range.contains(number)) {return true;}
}
return false;
}
public boolean removeRange(Range range) {
boolean isContains = ranges.remove(range);
rangeChangedProperty.firePropertyChange("Range", "-" , "removed");
return isContains;
}
/**
* Listen to {@link #rangeChangedProperty}. Fires whenever a range changes
* or removed.
* <br/>A client and a listener and when it fires, notify all threads.
*/
public void addChangeListener(PropertyChangeListener listener) {
rangeChangedProperty.addPropertyChangeListener(listener);
}
//represents a single range
//It is muttable
//can be implemented using ValueRange (https://stackoverflow.com/a/40716042/3992939)
class Range{
private SimpleIntegerProperty low = new SimpleIntegerProperty();
private SimpleIntegerProperty high = new SimpleIntegerProperty();
private SimpleObjectProperty<int[]> rangeProperty = new SimpleObjectProperty<>();
private Range(int rangeStart, int rangeEnd){
low.set(rangeStart) ; high.set(rangeEnd);
updateRange();
low.addListener((observable, oldValue, newValue) -> { updateRange(); });
high.addListener((observable, oldValue, newValue) -> { updateRange(); });
}
/**
* Listen to {@link #rangeProperty} that changes whenever the range changes
*/
void addListener(ChangeListener<int[]> listener) {
rangeProperty.addListener(listener);
}
private void updateRange() {rangeProperty.set(new int[] {low.get(), high.get()});}
public int getRangeStart() { return low.get(); }
public void setRangeStart(int rangeStart) { low.set(rangeStart);}
public int getRangeEnd() {return high.get();}
public void setRangeEnd(int rangeEnd) { high.set(rangeEnd);}
public boolean contains(int number){
int min = Math.min(low.get(), high.get());
int max = Math.max(low.get(), high.get());
return ((number >= min) && (number <= max));
}
}
}
<强> GetRange.java 强>
//used to simulate a thread trying to get a range
public class GetRange implements Runnable{
private Ranges ranges;
private int low, high;
private String id;
GetRange(Ranges ranges, int low, int high, String id) {
this.ranges = ranges;
this.low = low; this.high = high; this.id = id;
}
@Override
public void run() {
synchronized (ranges) {
while(ranges.getRange(low,high) == null) {
System.out.println("Tread "+ id + " is waiting");
try {
ranges.wait();
} catch (InterruptedException ex) { ex.printStackTrace();}
}
}
System.out.println("Tread "+ id + " got range. All done");
}
}
测试是:
//test
public static void main(String[] args) throws InterruptedException {
Ranges ranges = new Ranges();
ranges.addChangeListener( (evt) -> {
synchronized (ranges) {
ranges.notifyAll();
System.out.println(evt.getPropertyName() + " "+ evt.getNewValue());
}
});
Range range1 = ranges.getRange(10,15);
Range range2 = ranges.getRange(20,25);
new Thread(new GetRange(ranges, 10, 12, "A")).start();
new Thread(new GetRange(ranges, 21, 28, "B")).start();
new Thread(new GetRange(ranges, 10, 12, "C")).start();
Thread.sleep(50);
System.out.println("-- Changing end of range 1. Threads notifyied and keep waiting -----");
range1.setRangeEnd(16); //no thread effected
Thread.sleep(50);
System.out.println("-- Changing start of range 1. Threads notifyied and A or C get range -----");
range1.setRangeStart(13); //effects thread A or C
Thread.sleep(50);
System.out.println("-- Removing range 2. Threads notifyied and B get range -----");
ranges.removeRange(range2);//effects thread B
Thread.sleep(50);
System.exit(1);
}
输出:
踏板A正在等待正在等待踏板C正在等待
- 更改范围结束1.线程通知并保持等待-----
范围改变了 胎面B正在等待 Tread C正在等待 胎面A正在等待 - 更改范围的开始1.螺纹通知和A或C获取范围-----
范围已更改Tread获得范围。全部完成了 线程C正在等待 胎面B正在等待 - 取消范围2.螺纹通知,B取范围-----
范围已删除
胎面B得到了范围。全部完成了 Tread C正在等待
答案 2 :(得分:0)
Guava的Striped
locks可能会让您感兴趣。
如果你有一个函数int key(int p)
,它返回i
所属的区间[x_i,y_i]
的索引p
,你可以使用Striped
锁实现你的目标。
例如,如果我们将点x_1
,x_2
,... x_n
作为时间间隔限制,x_i < x_(i+1)
和x_(i+1) - x_i
保持不变从i
到1
的所有n
,我们都可以使用key(p) = p -> (p - x_1) / n
之类的内容。
但是,根据您选择的符号,这个假设可能不成立,函数key
也不那么简单 - 但希望锁定条带化解决方案对您有用。
答案 3 :(得分:0)
这是我支持读写锁定的IntervalLock的实现。读取可能获取具有重叠范围的锁定,而写入必须等待其范围与任何其他读取或写入重叠。基本思想是使用interval tree来存储范围。在给定时间,每个范围可以保持写锁定或多个读锁定。必须小心地从树中插入和删除范围以防止任何竞争条件。该代码使用来自here的区间树的实现。
<强> SemaphoreInterval.java 强>
package intervallock;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import datastructures.Interval;
public class SemaphoreInterval implements Interval {
private ArrayList<Semaphore> semaphores;
private int start;
private int end;
private int mode;
public SemaphoreInterval(int start, int end, int mode) {
this.semaphores = new ArrayList<>(1);
this.start = start;
this.end = end;
this.mode = mode;
}
public int getMode() {
return mode;
}
public ArrayList<Semaphore> getSemaphores() {
return semaphores;
}
@Override
public int start() {
return start;
}
@Override
public int end() {
return end+1;
}
}
<强> IntervalLock.java 强>
package intervallock;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Semaphore;
import datastructures.IntervalTree;
/**
* An implementation of Interval Lock
*
* @author Hieu
*
*/
public class IntervalLock {
public IntervalTree<SemaphoreInterval> tree;
private Semaphore treeLock;
private int maxPermits;
public static final int READ = 0;
public static final int WRITE = 1;
public IntervalLock(int maxPermits) {
tree = new IntervalTree<>();
treeLock = new Semaphore(1);
this.maxPermits = maxPermits;
}
/**
* Acquire a lock on range [start, end] with the specified mode.
* @param start The start of the interval
* @param end The end of the interval
* @param mode The mode, either IntervalLock.READ or IntervalLock.WRITE.
* @return The SemaphoreInterval instance.
*/
public SemaphoreInterval acquire(int start, int end, int mode) {
SemaphoreInterval si = new SemaphoreInterval(start, end, mode);
Set<Semaphore> semaphores = new HashSet<>();
try {
treeLock.acquire();
} catch (InterruptedException e) {
e.printStackTrace(System.out);
System.exit(-1);
}
Iterator<SemaphoreInterval> overlappers = tree.overlappers(si);
while (overlappers.hasNext()) {
SemaphoreInterval i = overlappers.next();
if (i == null) {
System.out.println("Error: Getting a null interval");
System.exit(-1);
}
if (i.compareTo(si) == 0)
continue;
switch (i.getMode()) {
case READ:
if (mode == WRITE)
semaphores.addAll(i.getSemaphores());
break;
case WRITE:
semaphores.addAll(i.getSemaphores());
break;
}
}
SemaphoreInterval result = tree.insert(si);
if (result != null)
si = result;
si.getSemaphores().add(new Semaphore(0));
treeLock.release();
for (Semaphore s: semaphores) {
try {
s.acquire();
} catch (InterruptedException e) {
e.printStackTrace(System.out);
System.exit(-1);
}
}
return si;
}
/**
* Release the range lock hold on specified SemaphoreInterval.
* @param si The semaphore interval returned by the acquire().
*/
public void release(SemaphoreInterval si) {
try {
treeLock.acquire();
} catch (InterruptedException e) {
e.printStackTrace(System.out);
System.exit(-1);
}
if (si.getSemaphores() == null || si.getSemaphores().size() == 0) {
System.out.println("Error: Empty array of semaphores");
treeLock.release();
return;
}
Semaphore sm = si.getSemaphores().remove(0);
if (si.getSemaphores().size() == 0) {
boolean success = tree.delete(si);
if (!success) {
System.out.println("Error: Cannot remove an interval.");
treeLock.release();
return;
}
}
treeLock.release();
sm.release(maxPermits);
}
}
<强>用法强>
// init the lock with the max permits per semaphore (should be the max number of threads)
public static final IntervalLock lock = new IntervalLock(1000);
// ...
// acquire the lock on range [a, b] (inclusive), with mode (either IntervalLock.READ or IntervalLock.WRITE)
// it returns a SemaphoreInterval instance
SemaphoreInterval si = lock.acquire(a, b, mode);
// ...
// release the acquired lock
lock.release(si);