JDK 原子操作类详解(AtomicInteger、AtomicIntegerArray等)
  fztgkkRjHIsV 2023年11月09日 20 0


当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,比如变量i=1,A线程更新i+1,B线程也更新i+1,经过两个线程操作之后可能i不等于3,而是等于2。因为A和B线程在更新变量i的时候拿到的i都是1,这就是线程不安全的更新操作,通常我们会使用synchronized来解决这个问题,synchronized会保证多线程不会同时更新变量i。


1. import java.util.concurrent.CountDownLatch;  
2.   
3. public class UnSafeAdd {  
4. private static int threadCount=10;  
5. private static CountDownLatch countDown=new CountDownLatch(threadCount);  
6. private static int count=0;  
7. private static class Counter implements Runnable{   
8. @Override  
9. public void run() {  
10. for(int i=0;i<1000;i++){  
11. //非原子操作  
12.             }   
13.             countDown.countDown();  
14.         }   
15.     }  
16. public static void main(String[] args) throws InterruptedException {  
17. new Thread[threadCount];  
18. for(int i=0;i<threadCount;i++){  
19. new Thread(new Counter());  
20.         }  
21. for(int i=0;i<threadCount;i++){  
22.             threads[i].start();;  
23.         }  
24.         countDown.await();  
25.         System.out.println(count);  
26.     }

输出:

8968


1. import java.util.concurrent.CountDownLatch;  
2.   
3.   
4. public class SafeAddWithSyn {  
5. private static int threadCount=10;  
6. private static CountDownLatch countDown=new CountDownLatch(threadCount);  
7. private static int count=0;  
8. synchronized private static void addCount(){//同步方法  
9.         count++;  
10.     }  
11. private static class Counter implements Runnable{   
12. @Override  
13. public void run() {  
14. for(int i=0;i<1000;i++){  
15.                 addCount();  
16.             }   
17.             countDown.countDown();  
18.         }   
19.     }  
20. public static void main(String[] args) throws InterruptedException {  
21. new Thread[threadCount];  
22. for(int i=0;i<threadCount;i++){  
23. new Thread(new Counter());  
24.         }  
25. for(int i=0;i<threadCount;i++){  
26.             threads[i].start();;  
27.         }  
28.         countDown.await();  
29.         System.out.println(count);  
30.     }  
31. }


输出:

10000

而Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都是使用Unsafe实现的包装类。


1. import java.util.concurrent.CountDownLatch;  
2. import java.util.concurrent.atomic.AtomicInteger;  
3.    
4. public class SafeAddWithAtomicInteger {  
5. private static int threadCount=10;  
6. private static CountDownLatch countDown=new CountDownLatch(threadCount);  
7. private static AtomicInteger count=new AtomicInteger(0);//原子操作类  
8. private static class Counter implements Runnable{   
9. @Override  
10. public void run() {  
11. for(int i=0;i<1000;i++){  
12. 1);  
13.             }   
14.             countDown.countDown();  
15.         }   
16.     }  
17. public static void main(String[] args) throws InterruptedException {  
18. new Thread[threadCount];  
19. for(int i=0;i<threadCount;i++){  
20. new Thread(new Counter());  
21.         }  
22. for(int i=0;i<threadCount;i++){  
23.             threads[i].start();;  
24.         }  
25.         countDown.await();  
26.         System.out.println(count.get());  
27.     }  
28. }


输出:

10000

原子更新基本类型

使用原子的方式更新基本类型,Atomic包提供了以下3个类。
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。
以上3个类提供的方法几乎一模一样,所以本节仅以AtomicInteger为例进行讲解,AtomicInteger的源码如下:


1. public class AtomicInteger extends Number implements java.io.Serializable {  
2. private static final long serialVersionUID = 6214790243416807050L;  
3.   
4. // setup to use Unsafe.compareAndSwapInt for updates 使用Unsafe类的CAS操作实现更新  
5. private static final Unsafe unsafe = Unsafe.getUnsafe();  
6. private static final long valueOffset;  
7.     ......  
8. //volatile保证可见性  
9. private volatile int value;  
10. //构造器  
11. public AtomicInteger(int initialValue) {  
12.         value = initialValue;  
13.     }  
14. public AtomicInteger() {  
15.     }  
16.     ......  
17.   
18. //CAS更新  
19. public final boolean compareAndSet(int expect, int update) {  
20. return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
21.     }  
22.     ......  
23. /*以下方法都使用循环CAS更新数据*/  
24. <pre name="code" class="java">    public final int getAndSet(int newValue) {//以原子方式设置新值  
25. for (;;) {  
26. int current = get();  
27. if (compareAndSet(current, newValue))  
28. return current;  
29.         }  
30.     }


public final int getAndIncrement() {//以原子方式自增 for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } ...... 

//以原子方式将输入值与当前值相加并返回结果 public final int addAndGet(int delta) { for (;;) {//循环 int current = get(); int next = current + delta; if (compareAndSet(current, next))//CAS return next; } } ......}


原子更新数组

通过原子的方式更新数组里的某个元素,Atomic包提供了以3类
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。




1. import java.util.concurrent.CountDownLatch;  
2.   
3. public class AtomicIntegerArrayTest {  
4. private static int threadCount=1000;  
5. private static CountDownLatch countDown=new CountDownLatch(threadCount);  
6. static int[] values=new int[10];  
7. private static class Counter implements Runnable{   
8. @Override  
9. public void run() {   
10. for(int i=0;i<100;i++){  
11. for(int j=0;j<10;j++){//所有元素+1  
12.                      values[j]++;  
13.                  }  
14.              }  
15.              countDown.countDown();  
16.         }   
17.     }   
18. public static void main(String[] args) throws InterruptedException{  
19. new Thread[threadCount];  
20. for(int i=0;i<threadCount;i++){  
21. new Thread(new Counter());  
22.         }  
23. for(int i=0;i<threadCount;i++){  
24.             threads[i].start();;  
25.         }  
26.         countDown.await();  
27. for(int i=0;i<10;i++){  
28. " ");  
29.         }  
30.     }  
31. }

输出:

99997 99996 99997 99997 99996 99996 99996 99996 99996 99996 


1. import java.util.concurrent.CountDownLatch;  
2. import java.util.concurrent.atomic.AtomicIntegerArray;  
3.   
4. public class AtomicIntegerArrayTest {  
5. private static int threadCount=1000;  
6. private static CountDownLatch countDown=new CountDownLatch(threadCount);  
7. static int[] values=new int[10];  
8. static AtomicIntegerArray ai=new AtomicIntegerArray(values);  
9. private static class Counter implements Runnable{   
10. @Override  
11. public void run() {   
12. for(int i=0;i<100;i++){  
13. for(int j=0;j<10;j++){//所有元素+1  
14.                      ai.getAndIncrement(j);   
15.                  }  
16.              }  
17.              countDown.countDown();  
18.         }   
19.     }   
20. public static void main(String[] args) throws InterruptedException{  
21. new Thread[threadCount];  
22. for(int i=0;i<threadCount;i++){  
23. new Thread(new Counter());  
24.         }  
25. for(int i=0;i<threadCount;i++){  
26.             threads[i].start();;  
27.         }  
28.         countDown.await();  
29. for(int i=0;i<10;i++){  
30. " ");  
31.         }  
32.         System.out.println();  
33. for(int i=0;i<10;i++){  
34. " ");  
35.         }  
36.     }  
37. }


输出:

100000 100000 100000 100000 100000 100000 100000 100000 100000 100000 
0 0 0 0 0 0 0 0 0 0 
需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。




1. //An {@code int} array in which elements may be updated atomically.  
2. public class AtomicIntegerArray implements java.io.Serializable {  
3.     ......  
4. private final int[] array;//存储数据的int数组  
5.   
6.     ......  
7. //构造器  
8. public AtomicIntegerArray(int length) {  
9. new int[length];  
10.     }  
11. public AtomicIntegerArray(int[] array) {  
12. // Visibility guaranteed by final field guarantees  
13. this.array = array.clone();//这里会复制传入的int数组,因此不会改变原来的int数组  
14.     }  
15.   
16. public final int length() {  
17. return array.length;  
18.     }  
19.     ......  
20. }


原子更新引用

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下3个类。
AtomicReference:原子更新引用类型。
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
AtomicMarkableReference:原子更新带有标记位的引用类型。


1. import java.util.concurrent.CountDownLatch;  
2. import java.util.concurrent.atomic.AtomicReference;  
3.   
4. public class Test {   
5. private static int threadCount=10;  
6. private static CountDownLatch countDown=new CountDownLatch(threadCount);  
7. public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();  
8. private static class ReferenceUpdater implements Runnable{   
9.         User user;  
10. public ReferenceUpdater(User user){  
11. this.user=user;  
12.         }  
13. @Override  
14. public void run() {  
15. for(int i=0;i<1000;i++){   
16.                 User oldUser=atomicUserRef.get();   
17.                 atomicUserRef.compareAndSet(oldUser, user);  
18.                 Thread.yield();  
19.             }   
20.             countDown.countDown();  
21.         }   
22.     }  
23. public static void main(String[] args) throws InterruptedException {  
24. new Thread[threadCount];  
25. for(int i=0;i<threadCount;i++){  
26. new Thread(new ReferenceUpdater(new User("name"+i,i)));  
27.         }  
28. for(int i=0;i<threadCount;i++){  
29.             threads[i].start();;  
30.         }  
31.         countDown.await();   
32.         System.out.println(atomicUserRef.get().getName());  
33.         System.out.println(atomicUserRef.get().getOld());  
34.     }  
35.   
36. static class User {  
37. private String name;  
38. private int old;  
39.   
40. public User(String name, int old) {  
41. this.name = name;  
42. this.old = old;  
43.         }  
44.   
45. public String getName() {  
46. return name;  
47.         }  
48.   
49. public int getOld() {  
50. return old;  
51.         }  
52.     }  
53. }

输出:

name1
1
每次输出结果都不确定,10种情况都有可能,但是name属性和age属性是匹配的。

1. /**An object reference that may be updated atomically.*/  
2. public class AtomicReference<V>  implements java.io.Serializable {  
3. <pre name="code" class="java">    ......


private static final Unsafe unsafe = Unsafe.getUnsafe();//用于CAS更新 ...... private volatile V value;//引用,volatile保证可见性 public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } //利用Unsafe类执行CAS操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } ...... 

//循环CAS public final V getAndSet(V newValue) { while (true) { V x = get(); if (compareAndSet(x, newValue)) return x; } } 

......}



原子更新字段

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下3个类进行原子字段更新。
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。

要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新类的字段(属性)必须使用public volatile修饰符。


1. import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;  
2.    
3. public class AtomicIntegerFieldUpdaterTest {   
4. // 创建原子更新器,并设置需要更新的对象类和对象的属性  
5. private static AtomicIntegerFieldUpdater<User> a =   
6. class, "old");  
7.    
8. public static void main(String[] args) throws InterruptedException {    
9. // 设置柯南的年龄是10岁  
10. new User("conan", 10);  
11. // 柯南长了一岁,但是仍然会输出旧的年龄  
12.         System.out.println(a.getAndIncrement(conan));  
13. // 输出柯南现在的年龄  
14.         System.out.println(a.get(conan));  
15.     }  
16.   
17. public static class User {  
18. private String name;  
19. public volatile int old;  
20.   
21. public User(String name, int old) {  
22. this.name = name;  
23. this.old = old;  
24.         }  
25.   
26. public String getName() {  
27. return name;  
28.         }  
29.   
30. public int getOld() {  
31. return old;  
32.         }  
33.     }  
34. }


输出:

10

11


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月09日 0

暂无评论

推荐阅读
fztgkkRjHIsV