Rumah  >  Artikel  >  Java  >  Bagaimana untuk menggunakan fungsi atomicity di Jawa?

Bagaimana untuk menggunakan fungsi atomicity di Jawa?

王林
王林ke hadapan
2023-05-09 16:40:17993semak imbas

Thread-safe

Apabila berbilang utas mengakses kelas, tidak kira kaedah penjadualan apa yang digunakan oleh persekitaran masa jalan atau cara proses ini akan berganti-ganti pelaksanaan, dan tidak memerlukan sebarang penyegerakan tambahan dalam kod panggilan Atau penyelarasan, kelas ini boleh menunjukkan tingkah laku yang betul, maka kelas ini dikatakan selamat untuk benang.

Keselamatan benang terutamanya dicerminkan dalam tiga aspek berikut

  • Atomicity: Menyediakan akses yang saling eksklusif, dan hanya satu thread boleh mengaksesnya di masa yang sama Ia beroperasi

  • Keterlihatan: Pengubahsuaian memori utama oleh satu utas boleh diperhatikan oleh utas lain dalam masa

  • Pemesanan: Suatu utas memerhati susunan pelaksanaan arahan dalam utas lain Disebabkan wujudnya penyusunan semula arahan, hasil pemerhatian secara amnya tidak kemas dan tidak teratur

Dalam. Penjelasan terperinci JUC tentang pakej Atomic

Pakej Atomic menyediakan banyak kelas Atomicxxx:

Bagaimana untuk menggunakan fungsi atomicity di Jawa?

Semuanya dilaksanakan oleh CAS (compareAndSwap ) Atom.

Mula-mula tulis contoh mudah seperti berikut:

@Slf4j
public class AtomicExample1 { 
    // 请求总数
    public static int clientTotal = 5000; 
    // 同时并发执行的线程数
    public static int threadTotal = 200; 
    public static AtomicInteger count = new AtomicInteger(0); 
    public static void main(String[] args) throws Exception {

        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }
 
    private static void add() {
        count.incrementAndGet();
    }
}

Anda boleh menghantar bahawa hasil setiap operasi sentiasa hasil yang dijangkakan sebanyak 5000 yang kami mahukan. Jelaskan bahawa kaedah pengiraan ini selamat untuk benang.

Mari kita lihat kaedah count.incrementAndGet() Parameter pertamanya ialah objek itu sendiri, dan parameter kedua ialah valueOffset, yang digunakan untuk merekodkan alamat terkumpul nilai itu sendiri dalam memori. Rekod ini juga terutamanya Ia adalah untuk mencari lokasi nilai dalam memori semasa operasi kemas kini untuk memudahkan perbandingan Parameter ketiga ialah pemalar 1

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
 
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
 
    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
 
    private volatile int value; 
 
    ... 此处省略多个方法...
 
    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
}

AtomicInteger kelas digunakan dalam kod sumber , yang menyediakan kaedah getAndAddInt Mari teruskan Klik untuk melihat kod sumbernya:

public final class Unsafe {
    private static final Unsafe theUnsafe;
 
    ....此处省略很多方法及成员变量.... 
 
 public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 
        return var5;
    } 
 
 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); 
 public native int getIntVolatile(Object var1, long var2);
}

Anda boleh melihat bahawa pernyataan do while digunakan untuk pelaksanaan utama. Teras penyataan while adalah untuk memanggil kaedah compareAndSwapInt(). Ia adalah kaedah asli, ia adalah kaedah peringkat rendah dan tidak dilaksanakan di Jawa.

Andaikan kami mahu melaksanakan operasi 0+1=0 Berikut ialah nilai setiap parameter dalam satu urutan:

Bagaimana untuk menggunakan fungsi atomicity di Jawa?

Bagaimana untuk menggunakan fungsi atomicity di Jawa?

Dikemas kini:

Bagaimana untuk menggunakan fungsi atomicity di Jawa?

Parameter pertama (var1) kaedah compareAndSwapInt() ialah objek semasa, Ini adalah kiraan dalam contoh kod. Pada masa ini nilainya ialah 0 (nilai dijangka). Nilai kedua (var2) ialah nilai nilaiOffset yang diluluskan, yang mempunyai nilai 12. Parameter ketiga (var4) ialah pemalar 1. Parameter pembolehubah (var5) dalam kaedah ialah nilai yang diperoleh dengan memanggil kaedah getIntVolatile yang mendasari berdasarkan parameter satu dan parameter dua valueOffset Pada masa ini, nilainya ialah 0. Matlamat yang ingin dicapai oleh compareAndSwapInt() ialah untuk objek kiraan, jika nilai dalam nilai jangkaan semasa var1 adalah sama dengan nilai yang dikembalikan asas (var5), kemudian kemas kini kepada nilai var5+var4. Jika berbeza, kitar semula untuk mendapatkan nilai yang dijangkakan (var5) sehingga nilai semasa adalah sama dengan nilai yang dijangkakan dan kemudian kemas kini. Teras kaedah compareAndSwap ialah apa yang biasa kita panggil CAS.

Prinsip pelaksanaan kelas lain di bawah pakej Atomic, seperti AtomicLong, pada asasnya adalah sama seperti di atas.

Di sini kami memperkenalkan kelas LongAdder Melalui analisis di atas, kami sudah mengetahui bahawa AtomicLong menggunakan CAS: ia sentiasa cuba mengubah suai nilai sasaran dalam gelung tak terhingga sehingga pengubahsuaian berjaya. Sekiranya persaingan tidak sengit, kebarangkalian untuk berjaya mengubah suai adalah sangat tinggi. Sebaliknya, jika kebarangkalian kegagalan pengubahsuaian adalah tinggi dalam situasi yang sangat kompetitif, ia akan membuat beberapa percubaan gelung, jadi prestasi akan terjejas.

Untuk jenis biasa pembolehubah panjang dan berganda, jvm membenarkan pemisahan operasi baca atau tulis 64-bit kepada dua operasi 32-bit. Idea teras LongAdder adalah untuk memisahkan data hotspot Ia boleh memisahkan nilai data teras dalaman AtomicLong ke dalam tatasusunan, dan memetakannya kepada salah satu nombor untuk mengira melalui cincangan dan algoritma lain apabila setiap utas mengaksesnya. Hasil pengiraan akhir ialah penjumlahan dan pengumpulan tatasusunan ini Antaranya, nilai data tempat liputan akan dipisahkan kepada berbilang sel Setiap sel mengekalkan nilai dalamannya secara bebas. . Dengan cara ini, titik panas dipisahkan dengan berkesan dan tahap selari dipertingkatkan. LongAdder adalah bersamaan dengan menyebarkan tekanan kemas kini satu titik ke setiap nod berdasarkan AtomicLong Pada masa keselarasan rendah, kemas kini terus ke pangkalan boleh memastikan prestasi pada asasnya konsisten dengan Atomic. Pada masa keselarasan yang tinggi, prestasi dipertingkatkan melalui desentralisasi. Walau bagaimanapun, jika terdapat kemas kini serentak semasa statistik, ia boleh menyebabkan ralat dalam data statistik.

Apabila kiraan konkurensi sebenar adalah tinggi, LongAdder boleh digunakan dahulu. AtomicLong boleh digunakan dahulu apabila paralelisme rendah atau nilai yang tepat diperlukan, yang lebih cekap.

Berikut ialah demonstrasi ringkas penggunaan mudah AtomicReference di bawah pakej Atomic:

@Slf4j
public class AtomicExample4 { 
    private static AtomicReference<Integer> count = new AtomicReference<>(0); 
    public static void main(String[] args) {
        count.compareAndSet(0, 2); 
        count.compareAndSet(0, 1); 
        log.info("count:{}", count.get());
    }
}

compareAndSet()分别传入的是预期值跟更新值,只有当预期值跟当前值相等时,才会将值更新为更新值;

上面的第一个方法可以将值更新为2,而第二个步中无法将值更新为1。

下面简单介绍下AtomicIntegerFieldUpdater 用法(利用原子性去更新某个类的实例):

@Slf4j
public class AtomicExample5 { 
    private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");
 
    @Getter
    private volatile int count = 100; 
    public static void main(String[] args) { 
        AtomicExample5 example5 = new AtomicExample5();
 
        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 1, {}", example5.getCount());
        }
 
        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 2, {}", example5.getCount());
        } else {
            log.info("update failed, {}", example5.getCount());
        }
    }
}

它可以更新某个类中指定成员变量的值。

注意:修改的成员变量需要用volatile关键字来修饰,并且不能是static描述的字段。

AtomicStampReference这个类它的核心是要解决CAS的ABA问题(CAS操作的时候,其他线程将变量的值A改成了B,接着又改回了A,等线程使用期望值A与当前变量进行比较的时候,发现A变量没有变,于是CAS就将A值进行了交换操作。

实际上该值已经被其他线程改变过)。

ABA问题的解决思路就是每次变量变更的时候,就将版本号加一。

看一下它的一个核心方法compareAndSet():

public class AtomicStampedReference<V> { 
    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
 
   ... 此处省略多个方法 ....
 
   public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
}

可以看到它多了一个stamp的比较,stamp的值是由每次更新的时候进行维护的。

再介绍下AtomicLongArray,它维护了一个数组。在该数组下,我们可以选择性的已原子性操作更新某个索引对应的值。

public class AtomicLongArray implements java.io.Serializable {
    private static final long serialVersionUID = -2308431214976778248L;
 
    private static final Unsafe unsafe = Unsafe.getUnsafe();
 
    ...此处省略....
 
 
    /**
     * Atomically sets the element at position {@code i} to the given value
     * and returns the old value.
     *
     * @param i the index
     * @param newValue the new value
     * @return the previous value
     */
    public final long getAndSet(int i, long newValue) {
        return unsafe.getAndSetLong(array, checkedByteOffset(i), newValue);
    }
 
    /**
     * Atomically sets the element at position {@code i} to the given
     * updated value if the current value {@code ==} the expected value.
     *
     * @param i the index
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int i, long expect, long update) {
        return compareAndSetRaw(checkedByteOffset(i), expect, update);
    }
}

最后再写一个AtomcBoolean的简单使用:

@Slf4j
public class AtomicExample6 { 
    private static AtomicBoolean isHappened = new AtomicBoolean(false);
 
    // 请求总数
    public static int clientTotal = 5000;
 
    // 同时并发执行的线程数
    public static int threadTotal = 200;
 
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("isHappened:{}", isHappened.get());
    }
 
    private static void test() {
        if (isHappened.compareAndSet(false, true)) {
            log.info("execute");
        }
    }
}

Atas ialah kandungan terperinci Bagaimana untuk menggunakan fungsi atomicity di Jawa?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam