簡體   English   中英

在沒有同步塊(即低成本鎖)的情況下,如何在一次安全操作中自動檢查Java中的兩個AtomicBooleans?

[英]How to atomically check TWO AtomicBooleans in Java in one safe operation without a synchronized block (i.e. low cost locks)?

所以我有兩個AtomicBoolean,我需要檢查兩個。 像這樣:

if (atomicBoolean1.get() == true && atomicBoolean2.get() == false) {

   // ...
}

但是在兩者之間有一個競爭條件:(

有沒有一種方法可以在不使用同步的情況下將兩個原子布爾檢查合並為一個檢查(即同步塊)?

好吧,我可以想到幾種方法,但這取決於您所需的功能。

一種方法是“作弊”並使用AtomicMarkableReference <Boolean>

final AtomicMarkableReference<Boolean> twoBooleans = (
    new AtomicMarkableReference<Boolean>(true, false)
);

void somewhere() {
    boolean b0;
    boolean[] b1 = new boolean[1];
    b0 = twoBooleans.get(b1);

    b0 = false;
    b1[0] = true;

    twoBooleans.set(b0, b1);
}

但這有點痛苦,只能讓您獲得兩個價值。

因此,您可以將AtomicInteger與位標志一起使用:

static final int FLAG0 = 1;
static final int FLAG1 = 1 << 1;

final AtomicInteger intFlags = new AtomicInteger(FLAG0);

void somewhere() {
    int flags = intFlags.get();

    int both = FLAG0 | FLAG1;
    if((flags & both) == FLAG0) { // if FLAG0 has a 1 and FLAG1 has a 0
        something();
    }

    flags &= ~FLAG0; // set FLAG0 to 0 (false)
    flags |=  FLAG1; // set FLAG1 to 1 (true)

    intFlags.set(flags);
}

也有點痛苦,但它可以為您提供32個值。 如果您確實需要,可以圍繞它創建一個包裝器類。 例如:

public class AtomicBooleanArray {
    private final AtomicInteger intFlags = new AtomicInteger();

    public void get(boolean[] arr) {
        int flags = intFlags.get();

        int f = 1;
        for(int i = 0; i < 32; i++) {
            arr[i] = (flags & f) != 0;
            f <<= 1;
        }
    }

    public void set(boolean[] arr) {
        int flags = 0;

        int f = 1;
        for(int i = 0; i < 32; i++) {
            if(arr[i]) {
                flags |= f;
            }
            f <<= 1;
        }

        intFlags.set(flags);
    }

    public boolean get(int index) {
        return (intFlags.get() & (1 << index)) != 0;
    }

    public void set(int index, boolean b) {
        int f = 1 << index;

        int current, updated;
        do {
            current = intFlags.get();
            updated = b ? (current | f) : (current & ~f);
        } while(!intFlags.compareAndSet(current, updated));
    }
}

很好 也許在get中復制數組時執行了一個設置,但要點是您可以原子獲取設置所有32個。 (compare和set的do-while循環很丑陋,但這是原子類自身如何處理諸如getAndAdd之類的東西的方法 。)

在這里,AtomicReference似乎不切實際。 它允許原子獲取和設置,但是一旦您使用內部對象,就不再需要原子更新。 您每次都必須創建一個全新的對象。

final AtomicReference<boolean[]> booleanRefs = (
    new AtomicReference<boolean[]>(new boolean[] { true, true })
);

void somewhere() {
    boolean[] refs = booleanRefs.get();

    refs[0] = false; // not atomic!!

    boolean[] copy = booleanRefs.get().clone(); // pretty safe
    copy[0] = false;
    booleanRefs.set(copy);
}

如果要自動對數據執行臨時操作(獲取->更改->設置,而不會產生干擾),則必須使用鎖定或同步。 我個人會使用鎖定或同步,因為通常情況下,整個更新就是您要保留的內容。

**不安全! **

不要這樣!

這可以(可能)通過sun.misc.Unsafe完成。 這是一個使用不安全的類來寫兩個易變的長牛仔風格的類。

public class UnsafeBooleanPair {
    private static final Unsafe UNSAFE;

    private static final long[] OFFS = new long[2];
    private static final long[] MASKS = new long[] {
        -1L >>> 32L, -1L << 32L
    };

    static {
        try {
            UNSAFE = getTheUnsafe();

            Field pair = UnsafeBooleanPair.class.getDeclaredField("pair");
            OFFS[0] = UNSAFE.objectFieldOffset(pair);
            OFFS[1] = OFFS[0] + 4L;

        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    private volatile long pair;

    public void set(int ind, boolean val) {
        UNSAFE.putIntVolatile(this, OFFS[ind], val ? 1 : 0);
    }

    public boolean get(int ind) {
        return (pair & MASKS[ind]) != 0L;
    }

    public boolean[] get(boolean[] vals) {
        long p = pair;
        vals[0] = (p & MASKS[0]) != 0L;
        vals[1] = (p & MASKS[1]) != 0L;
        return vals;
    }

    private static Unsafe getTheUnsafe()
    throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        return (Unsafe)theUnsafe.get(null);
    }
}

重要的是, Open JDK源代碼中的fieldOffsetJavadoc表示不要對偏移量進行算術運算。 但是,對它進行算術運算似乎很有效,因為我不會得到垃圾。

這樣可以對整個單詞進行一次易失性讀取,但是(可能)對其中任何一半進行易失性寫入。 潛在的putByteVolatile可以用於將很長的段分成8個段。

我不建議任何人使用此功能(不要使用此功能!),但是這很有趣。

我只能想到兩種方式:使用AtomicInteger的低兩位或使用自旋鎖。 我認為Hotspot可以自行優化某些鎖,直至自旋鎖。

使用

 Lock l = ...;
 l.lock();
 try {
     // access the resource protected by this lock
 } finally {
     l.unlock();
 }

從技術上講,它不是一個同步塊,即使它是一種同步形式,我認為您要的是同步的定義,所以我認為“不進行同步”是不可能的。

暫無
暫無

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

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