java線程安全性-原子13個操作類

(一)原子類簡介     
     當程序更新一個,如果多程同更新量,可能得到期望之外的,比如變量i=1A程更新i+1B程也更新i+1經過兩個程操作之後可能i不等於3,而是等於2。因 AB程在更新i候拿到的i都是1就是程不安全的更新操作,通常我會使 synchronized來解決問題synchronized會保程不會同更新i
      JavaJDK 1.5開始提供了java.util.concurrent.atomic包(以下Atomic包),個包中 的原子操作提供了一種用法簡單、性能高效、程安全地更新一個量的方式。
      爲變量的型有很多種,所以在Atomic包裏一共提供了13,屬於4型的原子更 新方式,分是原子更新基本型、原子更新數、原子更新引用和原子更新屬性(字段)。 Atomic包裏的基本都是使用Unsafe實 現的包裝
 
(二)原子更新基本類型類
 
1.atomic包下的所有類示意圖
2.使用原子的方式更新基本型,Atomic包提供了以下3類。
  • AtomicBoolean:原子更新布
  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新整型
以上3提供的方法幾乎一模一,所以AtomicInteger解、AtomicInteger的常用方法如下:
  • int addAndGetint delta):以原子方式將入的數例中的AtomicInteger裏的 value)相加,並返回果。
  • boolean compareAndSetint expectint update):如果入的數等於以原子方式將該值設爲輸入的
  • int getAndIncrement():以原子方式將當前1,注意,裏返回的是自增前的,等價於i++。
  • int incrementAndGet():以原子方式將當前1,等價於++1

3.AtomicBoolean案例演示  參考:AtomicBoolean介紹與使用

@Slf4j
@ThreadSafe
public class AtomicBooleanTest {
    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");
        }
    }
}

4.AtomicInteger案例演示

        //結果:i = 10  j = 20
        AtomicInteger atomicInteger = new AtomicInteger();
        int i = atomicInteger.addAndGet(10);
        int j = atomicInteger.addAndGet(10);
        System.out.println("i = " + i);
        System.out.println("j = " + j);

        //等價於i++  reuslt1 = 0
        AtomicInteger atomicInteger1 = new AtomicInteger();
        int reuslt1 = atomicInteger1.getAndIncrement();
        System.out.println("reuslt1 = " + reuslt1);

        //等價於++i  reuslt2 = 1
        AtomicInteger atomicInteger2 = new AtomicInteger();
        int reuslt2 = atomicInteger2.incrementAndGet();
        System.out.println("reuslt2 = " + reuslt2);

        //result3 = 100
        AtomicInteger atomicInteger3 = new AtomicInteger(10);
        atomicInteger3.getAndSet(100);
        int result3 = atomicInteger3.get();
        System.out.println("result3 = " + result3);

        //CAS樂觀鎖和數據庫樂觀鎖類似 b1 = false  b2 = true
        AtomicInteger atomicInteger4 = new AtomicInteger(10);
        boolean b1 = atomicInteger4.compareAndSet(11, 100);
        boolean b2 = atomicInteger4.compareAndSet(10, 100);
        System.out.println("b1 = " + b1);
        System.out.println("b2 = " + b2);

5.使用原子類確保線程安全

@NotThreadSafe
public class UnsafeSequences {
  //多個線程併發訪問共享變量n,線程不安全  
  private int n;
  private  int add(){
      return n++;
  }
    public static void main(String[] args) {
        Thread t ;
        UnsafeSequences unsafeSequences =new UnsafeSequences();
        for (int i = 0; i <10000 ; i++) {
           t= new Thread(new Runnable() {
                @Override
                public void run() {
                    int  reuslt = unsafeSequences.add();
                    System.out.println("reuslt = " + reuslt);
                }
            });
            t.start();
        }
    }
}
@ThreadSafe
public class SafeSequence {
 private static AtomicInteger atomicInteger =new AtomicInteger();
  private  int add(){
      return atomicInteger.getAndIncrement();
  }

    public static void main(String[] args) throws InterruptedException {
       Thread t ;
        for (int i = 0; i <10000 ; i++) {
           t= new Thread(new Runnable() {
                @Override
                public void run() {
                   new SafeSequence().add();
                }
            });
            t.start();
            t.join();
        }
        System.out.println(atomicInteger.get());
    }
}

6.getAndIncrement方法實現原子操作原理分析

 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

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;
    }

var1:指的是AtomicInteger對象

var2:指的是valueOffset內存首地址偏移量--當前值

var4:指的是常量1

var5:內存中的值--期望值

當var2當前值和var5期望值相等的情況下,將值更新爲var5+var4

(三)原子更新數組

1.通原子的方式更新數裏的某個元素,Atomic包提供了以下4

  • AtomicIntegerArray:原子更新整型數裏的元素
  • AtomicLongArray:原子更新整型數裏的元素
  • AtomicReferenceArray:原子更新引用型數裏的元素
AtomicIntegerArray主要是提供原子的方式更新數裏的整型,其常用方法如下:
  • ·int addAndGetint iint delta):以原子方式將與數中索引i的元素相加
  • boolean compareAndSetint iint expectint update):如果當前等於以原子
    方式將數位置i的元素置成update

2.案例演示

@ThreadSafe
public class AtomicIntegerArrayDemo {
    public static void main(String[] args) {
        int[] a = {5,6,7,8,9};
        AtomicIntegerArray array = new AtomicIntegerArray(a);
        array.addAndGet(0,1);
        int result = array.get(0);
        //result = 6
        System.out.println("result = " + result);
        //array = [6, 6, 7, 8, 9]
        System.out.println("array = "+array.toString());

        //array = [5, 6, 7, 8, 9]
        array.compareAndSet(0,6,5);
        System.out.println("array = "+array.toString());

    }
}

(四) 原子更新引用類型

        原子更新基本型的AtomicInteger,只能更新一個量,如果要原子更新多個量,就需 要使用個原子更新引用型提供的Atomic包提供了以下3
  • AtomicReference:原子更新引用
  • AtomicReferenceFieldUpdater:原子更新引用型裏的字段
  • AtomicMarkableReference:原子更新標記位的引用型。可以原子更新一個布型的標記位和引用型。構造方法是AtomicMarkableReferenceV initialRefboolean initialMark

1.AtomicReference案例演示

public class AtomicReferenceTest {
   private static AtomicReference atomicReference = new AtomicReference();
    public static void main(String[] args) {
        Person person1 = new Person();
        person1.setName("zhangsan");
        person1.setAge(18);
        atomicReference.set(person1);

        Person person2 = new Person();
        person2.setName("lisi");
        person2.setAge(22);

        atomicReference.compareAndSet(person1,person2);
        //結果:Person{name='lisi', age=22}
        System.out.println(atomicReference);
    }

    static  class Person{
        private String name;
        private int age;
    }
}

2.AtomicReferenceFieldUpdater案例演示

public class AtomicReferenceFieldUpdaterTest {
   private static AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Person.class,String.class,"name");
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("zhangsan");
        person.setAge(18);

        updater.compareAndSet(person, person.getName(),"lisi");
        //結果:name = lisi
        System.out.println("name = "+person.getName());

        //將lisi修改爲wangwu
        updater.getAndSet(person,"wangwu");
        System.out.println("name = "+person.getName());
    }

    static class Person{

        volatile String name;
        volatile int age;
    }
}

(五)原子更新類字段

如果需原子地更新某個裏的某個字段,就需要使用原子更新字段Atomic包提供了以下3類進行原子字段更新。
  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
  • AtomicLongFieldUpdater:原子更新整型字段的更新器
  • AtomicStampedReference:原子更新有版本號的引用型。該類將整數與引用關
    來,可用於原子的更新數據和數據的版本號,可以解決使用CAS行原子更新可能出
    ABA問題
要想原子地更新字段需要兩步:
第一步,因原子更新字段都是抽象,每次使用的時候必使用靜方法newUpdater()建一個更新器,並且需要置想要更新的和屬性。
二步,更新的字段(屬性)必使用public volatile符。
 
AtomicIntegerFieldUpdater案例演示
@Slf4j
public class AtomicIntegerFieldUpdaterTest {
    //第一步,因爲原子更新字段類都是抽象類,每次使用的時候必須使用靜態方法newUpdater()創建一個更新器,並且需要設置想要更新的類和屬性。
    private static AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterTest> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdaterTest.class, "count");

    //第二步,更新類的字段(屬性)必須使用public volatile修飾符。
    @Getter
    public volatile int count = 100;

    public static void main(String[] args) {

        AtomicIntegerFieldUpdaterTest updaterTest = new AtomicIntegerFieldUpdaterTest();

        if (updater.compareAndSet(updaterTest, 100, 120)) {
            //update success 1, 120
            log.info("update success 1, {}", updaterTest.getCount());
        }

        if (updater.compareAndSet(updaterTest, 100, 120)) {
            log.info("update success 2, {}", updaterTest.getCount());
        } else {
            // update failed 3, 120
            log.info("update failed 3, {}", updaterTest.getCount());
        }
    }
}

(六)Atomic包下相關面試題目

 

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章