区间锁实现

时间:2017-10-06 00:30:25

标签: java multithreading performance locking wait

我正在寻找间隔锁的实现。给定间隔(x, y),如果没有其他人正在获取包含点p x <= p <= y的任何间隔,则线程可以获取锁。

我目前的想法是维护一系列现有授权时间间隔(x1, y1, x2, y2, ..., xn, yn) x1 < y1 < x2 < y2 < ... < xn < yn,并检查(x, y)是否与这些时间间隔重叠。

搜索需要O(logn)次,这让我感到高兴。但是,当搜索返回有一些重叠时,lock函数需要以某种方式有效地重试,直到它可以在其他人释放其间隔锁时获取锁。忙碌的等待或睡眠似乎不是一个好主意。

有没有办法有效地实施重试?

4 个答案:

答案 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_1x_2,... x_n作为时间间隔限制,x_i < x_(i+1)x_(i+1) - x_i保持不变从i1的所有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);