Javaのアトミック操作クラス、あなたはどのくらい知っていますか?

はじめにアトミック操作クラス

ので、悲観的ロックが使用され同期ストラテジーを、それが特に効率的な解決策ではありません。実際には、JUC原子パケットで操作の簡単なシーケンス、高い性能を提供し、変数の基本的なタイプを更新するために、スレッドの安全性クラスを確保するために、アレイ素子、および参照フィールドタイプのオブジェクトタイプを更新します。これらのクラスは、原子パケットで採用されている楽観的ロック戦略アトミックデータ更新に、CAS操作はJava実装で使用されています。

CAS

共有データには、他のスレッドの競合が存在しない場合に行うには、まず、操作が成功したことを、そうでない場合は補償措置をとる(それが成功するまで再試行を続け:ハードウェアの命令セットの開発により、我々は、衝突検出に基づく楽観的同時方式を使用することができます今のところ)。多くは、この楽観的同時方式は、スレッドがブロックされている必要はありません実現するため、この操作は、同期非ブロック同期と呼ばれています。オプティミスティック・ロックに必要な操作とこの2つのステップの衝突検出は、アトミック持って、あなただけのハードウェアに依存することができ、同期を確保するためにミューテックスを使用することはできません。アトミック操作のためのハードウェアサポートは、最も典型的なものである:コンペア・アンド・スワップ(比較交換、CAS )。CAS命令は3つのオペランド、すなわちメモリアドレスV、古いものと新しい値BがAの期待値が必要です 動作はVの値が等しい場合にのみ実行されると、それはV Bの値を更新します

//既知のCAS 
の@ VAR1オブジェクトが属するは、(使用される実際のオフセットアドレスが達成される)VAR2必要な値を比較し、比較値である
オブジェクトVAR1のVAR2オフセット値がVAR4に等しい場合// 、その値はVAR5に設定され、偽等しくないVAR4が返された場合、真を返します。
公共の最終ネイティブブールcompareAndSwapInt(オブジェクトVAR1 、長いVAR2、int型VAR4、int型VAR5)。
  • CASの問題

1.ABA問題

初期値を読み取る変数は、その値がBに変更され、後で戻ってAに変更された場合、CAS操作が誤って、それが変更されたことがないと信じています。

JUCパッケージがこの問題を解決するための参照クラスAtomicStampedReferenceでマークされた原子を提供し、それは、変数値のCASバージョンの正しさを制御することで確保することができます。、ABAの問題を解決する伝統的な相互排他の同期を使用する必要がアトミッククラスよりも効率的である可能性がある場合ABAの問題は、ほとんどの場合、プログラムの同時妥当性には影響を与えません。

2.スピン長いオーバーヘッドの大きな時間

CASスピン(すなわち、サイクルまで失敗した実行が成功している)長い時間のために成功していない場合、それは非常に大規模な実行オーバーヘッドのCPUをもたらすでしょう。いくつかの効率改善が存在することになるので、JVMは、プロセッサによって提供される一時停止命令をサポートできる場合、一時停止コマンドは2つの目的があり、最初に、命令のパイプライン実行(デパイプライン)を遅らせることができる、CPUは、実装のためにあまりにも多くのリソースを消費しません、時間依存の実装を遅らせる、プロセッサの数の遅延時間はゼロです。CPUパイプラインによって引き起こされる順序によるメモリの競合に(メモリ順序違反)はCPUの効率を向上させるために、(CPUのパイプラインフラッシュ)がクリアされたときには、第2の出口ループを回避することができます。

3.共有変数はアトミック操作を保証できる 唯一の単一の共有変数のCASが有効な操作が無効とCAS間で共有変数の複数を含み、。しかし、JDK 1.5から、オブジェクト間のAtomicReferenceクラスリファレンス原子を確保するために設けられている、それはCAS操作が行われた変数の複数のオブジェクトにパッケージ化することができるので、我々はロックを使用することができ、またはベースAtomicReferenceがカプセル化された共有変数の複数共有変数が動作します。

  • 同期VS CAS

同期のベテラン(非最適化前の)主な質問は次のとおりです。ですので、ブロッキングとパフォーマンスの問題のスレッドウェイクロックは、スレッドの競争の存在下で提起される相互に排他的な同期(ブロッキング同期)CASは、任意のスレッドではないCASは、特定の操作が試みに失敗した実施ではなく、保留中の操作の時間がかかる覚醒、したがっても呼ばれる時ハング非ブロッキング同期これは、二つの主な違いです。

アトミック更新基底クラス

次のようにアトミックアトミック更新パッケージは、実質的に、ユーティリティクラスの授業を改善します:

AtomicBoolean //アトミックブール更新更新

アトミック整数更新更新// AtomicIntegeを

AtomicLong //ロングを更新アトミック更新
  • 一般的に使用される方法を要約する例のAtomicInteger。

addAndGet(INTデルタ)//アトミック入力値と元の値のインスタンスを追加することにより、最終的な結果を返し

incrementAndGetを()//アトミック元の値は、実施例1の動作、およびリターンに追加されます添加の最終結果は、

getAndSet(INT newValueに)//新しい値にインスタンスを更新して戻す古い値

getAndIncrement()//例1の方法は、元の値に加え原子は、リターンがインクリメントされます古い値の前に

AtomicInteger getAndIncrement()メソッドのソースコードを次のように

公共最終INT getAndIncrement(){ 
    unsafe.getAndAddInt(この、valueOffset、1)を返します。
}

実際には、静的メソッド危険なクラスgetUnsafeが危険なインスタンスを取得することによって得られgetAndAddInt方法危険なインスタンスを、と呼ばれます:

private static final Unsafe unsafe = Unsafe.getUnsafe();
public class AtomicIntegerDemo {
    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    //java.util.concurrent.atomic.AtomicInteger;
    public static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Semaphore和CountDownLatch模拟并发
        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) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count:{"+count.get()+"}");
    }

    public static void add() {
        count.incrementAndGet();
    }
}

输出结果:

count:{5000}

AtomicLong的实现原理和AtomicInteger一致,只不过一个针对的是long变量,一个针对的是int变量。 而boolean变量的更新类AtomicBoolean类是怎样实现更新的呢?核心方法是compareAndSet()方法,其源码如下:

public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;
    int u = update ? 1 : 0;
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

可以看出,compareAndSet方法的实际上也是先转换成0,1的整型变量, 然后是通过针对int型变量的原子更新方法compareAndSwapInt来实现的。 可以看出atomic包中只提供了对boolean,int ,long这三种基本类型的原子更新的方法, 参考对boolean更新的方式,原子更新char,doule,float也可以采用类似的思路进行实现。

原子更新数组

atomic包下提供能原子更新数组中元素的类有:

AtomicIntegerArray //原子更新整型数组中的元素

AtomicLongArray //原子更新长整型数组中的元素

AtomicReferenceArray //原子更新引用类型数组中的元素

这几个类的用法一致,就以AtomicIntegerArray来总结下常用的方法:

getAndAdd(int i, int delta) //以原子更新的方式将数组中索引为i的元素与输入值相加

getAndIncrement(int i) //以原子更新的方式将数组中索引为i的元素自增加1

compareAndSet(int i, int expect, int update) //将数组中索引为i的位置的元素进行更新

可以看出,AtomicIntegerArray与AtomicInteger的方法基本一致, 只不过在AtomicIntegerArray的方法中会多一个指定数组索引位i。

public class AtomicIntegerArrayDemo {
    private static int[] value = new int[]{1, 2, 3};
    private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        //对数组中索引为1的位置的元素加5
        int result = integerArray.getAndAdd(1, 5);
        System.out.println(integerArray.get(1));
        System.out.println(result);
    }
}

输出结果:

7
2

原子更新引用类型

如果需要原子更新引用类型变量的话,为了保证线程安全,atomic也提供了相关的类:

AtomicReference //原子更新引用类型

AtomicReferenceFieldUpdater //原子更新引用类型里的字段

AtomicMarkableReference //原子更新带有标记位的引用类型

这几个类的使用方法也是基本一样的,以AtomicReference为例。

public class AtomicReferenceDemo {

    private static AtomicReference<User> reference = new AtomicReference<>();

    public static void main(String[] args) {
        User user1 = new User("a", 1);
        reference.set(user1);
        User user2 = new User("b",2);
        User user = reference.getAndSet(user2);
        System.out.println(user);
        System.out.println(reference.get());
    }

    static class User {
        private String userName;
        private int age;

        public User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

输出结果:

User{userName='a', age=1}
User{userName='b', age=2}

首先将对象User1用AtomicReference进行封装,然后调用getAndSet方法, 从结果可以看出,该方法会原子更新引用的user对象, 变为User{userName='b', age=2},返回的是原来的user对象User{userName='a', age=1}。

原子更新字段类型

如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:

AtomicIntegeFieldUpdater //原子更新整型字段类

AtomicLongFieldUpdater //原子更新长整型字段类

AtomicStampedReference //原子更新引用类型,这种更新方式会带有版本号。
// 而为什么在更新的时候会带有版本号,是为了解决CAS的ABA问题;

要想使用原子更新字段需要两步操作:

  • 原子更新字段类都是抽象类,只能通过静态方法newUpdater来创建一个更新器,并且需要设置想要更新的类和属性

  • 更新类的属性必须使用public volatile进行修饰

这几个类提供的方法基本一致,以AtomicIntegerFieldUpdater为例。

public class AtomicIntegerFieldUpdaterDemo {
    private static AtomicIntegerFieldUpdater updater =
        AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
    
    public static void main(String[] args) {
        User user = new User("a", 1);
        int oldValue = updater.getAndAdd(user, 5);
        System.out.println(oldValue);
        System.out.println(updater.get(user));
    }

    static class User {
        private String userName;
        public volatile int age;

        public User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                    "userName='" + userName + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

输出结果:

1
6

创建AtomicIntegerFieldUpdater是通过它提供的静态方法进行创建, getAndAdd方法会将指定的字段加上输入的值,并且返回相加之前的值。 user对象中age字段原值为1,加5之后,可以看出user对象中的age字段的值已经变成了6。


免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
传送门: mp.weixin.qq.com/s/Jzdd


おすすめ

転載: blog.51cto.com/13672983/2401479