簡體   English   中英

Java中的Threadsafe雙向關聯

[英]Threadsafe bidirectional association in Java

實現線程安全的雙向關聯有什么好方法? 是否有一個好的庫或代碼生成器?

這是一個非線程安全的示例:

class Foo {

    private Foo other;

    public Foo getOther() {
        return other;
    }

    public void setOther(Foo other) {
        this.setOtherSecretly(other);
        other.setotherSecretly(this);
    }

    void setOtherSecretly(Foo other) {
        if (this.other != null) this.other.other = null;
        this.other = other;
    }
}

我對線程安全的要求是:

  • 沒有死鎖
  • 最終的一致性(當所有線程停止修改對象時,最終會達到一致狀態。即,當另一個線程同時執行setOther時, assert foo.getOther().getOther() == foo是可以接受的。
  • 順序行為。 如果線程執行setOther而沒有其他其他線程覆蓋該值,則getOther立即返回該線程的新值。
  • 沒有時光倒流。 一旦線程使用getOther觀察到新值,它將永遠不會再次接收舊值(除非再次設置)。

也很高興有:

  • 低爭用,尤其是沒有全局鎖定。 解決方案應該很好地擴展。
  • 盡可能少的同步開銷。 對於單個線程,它應該具有合理的性能。
  • 內存開銷低。 當一個對象有5個關聯時,我不希望每個關聯有3個額外的字段。 setter中的局部變量是可以的。

我的應用程序將有16個線程處理大約5.000個幾個類的對象。

我還沒有想出一個解決方案(不,這不是功課),所以任何輸入(想法,文章,代碼)都是受歡迎的。

Google Guava為您做到這一點: BiMap

例如:

BiMap<Integer, String> bimap = Synchronized.biMap(HashBiMap.create(), someMutexObject);
bimap.put(1, "one");
bimap.put(2, "two");

bimap.get(1); // returns "one"
bimap.inverse().get("one") // returns 1

someMutexObject可以是您想要synchronize任何對象。

您可以將每個對象關聯到自己的鎖定,然后在獲取兩個鎖定時設置另一個對象。 例如。 為避免死鎖,您可以使用鎖定順序

class Foo extends ReentrantLock {

    private static final AtomicInteger order = new AtomicInteger(0);

    final int id = order.incrementAndGet();

    private Foo other;

    public Foo getOther() {
        return other;
    }

    public void setOther(Foo other) {
        if (id > other.id) {
            other.lock();
            try {
                this.lock();
                try {

                    // assign here
                } finally {
                    this.unlock();
                }
            } finally {
                other.unlock();
            }
        } else if (id < other.id) {
            this.lock();
            try {
                other.lock();
                try {

                    // assign here
                } finally {
                    other.unlock();
                }
            } finally {
                this.unlock();
            }
        }
    }
}

試試這個,在沒有寫完的情況下允許閱讀。

的ReentrantReadWriteLock

另一種選擇是簡單地使other參考不穩定。 這將滿足您的要求和您的好處。

我可以想到一個靜態成員作為一個監視器。 但也許這就是你認為'全球'的鎖定。

class Foo {

    private static final Object MONITOR = new Object();
    private Foo other;

    public Foo getOther() {
        synchronized(MONITOR){
            return other;
        }
    }

    public void setOther(Foo other) {
        synchronized(MONITOR){
            this.setOtherSecretly(other);
            other.setotherSecretly(this);
        }
    }

    void setOtherSecretly(Foo other) {
        if (this.other != null) this.other.other = null;
        this.other = other;
    }
}

事實證明這是一個非常難的問題! (很好!)使用全局鎖定太簡單了,而且可能太慢了。 我想我有一個無鎖版本 - 我將在下面介紹 - 但我不會過分相信它是完美的。 所有可能的交錯都很難推理。

事實證明,這是事務性內存的完美用例! 只需將整個塊標記為原子並修改您想要的任何內容! 你可能會看看Deuce STM ,雖然我不知道它有多快。 如果只有最好的系統不需要定制硬件......

無論如何,在思考了這個問題一段時間之后,我想我想出了一個使用Java的AtomicReference來繞過鎖的版本。 一,代碼:

class Foo {

    private AtomicReference<Foo> oRef = new AtomicReference<Foo>;

    private static final AtomicInteger order = new AtomicInteger(0);
    private final int id = order.incrementAndGet();

    private static bool break(Foo x, Foo y) {
        if (x.id > y.id)
            return break(y, x);

        return x.oRef.compareAndSet(y, null) &&
               y.oRef.compareAndSet(x, null);
    }

    public void setOther(Foo f) {
        if (f != null && f.id > id) {
            f.setOther(this);
            return;
        }
        do {
            Foo other = oRef.get();
            if (other == f)
                break;

            if (other != null && !break(this, other))
                continue;

            if (f == null)
                break;

            Foo fother = f.oRef.get();
            if (fother != null && !break(f, fother))
                continue;

            if (!f.oRef.compareAndSet(null, this))
                continue;
            if (!oRef.compareAndSet(null, f)) {
                f.oRef.set(null);
                continue;
            }
        } while (false);
    }
}

關鍵點:

  • 如果沒有任何並發​​訪問任何受影響的Foos(最多4個),則setter會通過循環修改相關指針。
  • 在存在並發setter的情況下,某些setter可能會失敗並重試。
  • 如果多個線程試圖同時斷開關系,則只有一個線程將成功執行x.oRef.compareAndSet(y, null)
  • 如果f.oRef.compareAndSet(null, f)成功,則沒有其他線程能夠破壞break()的半成熟關系。 然后,如果oRef.compareAndSet(null, f)成功,則操作完成。 如果失敗,可以重置f.oRef並重試每個人。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM