zoukankan      html  css  js  c++  java
  • 【Java多线程】Java中的13个原子操作类(十九)

    一、Atomic包       

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

      而Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。 

      因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都是使用Unsafe实现的包装类。

    二、原子更新基本实现类

      1、使用原子的方式更新基本类型,Atomic包提供了以下3个类。 

    • AtomicBoolean:原子更新布尔类型。 

    • AtomicInteger:原子更新整型。 

    • AtomicLong:原子更新长整型。 

         以上3个类提供的方法几乎一模一样,所以这里仅以AtomicInteger为例进行讲解:

    • int addAndGet(int delta):以原子方式将输入和数值与实例中的值(AtomicInteger里的value)相加,并返回结果。

    • boolean compareAndSet(int expect, int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。

    • int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值

    • void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读取到旧的值。

    • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

    示例:

    1 public class AtomicIntegerTest {
    2 
    3     public static void main(String[] args) {
    4         AtomicInteger ai = new AtomicInteger(1);
    5 
    6         System.out.println(ai.getAndIncrement());
    7         System.out.println(ai.get());
    8     }
    9

      输出:

    1 1
    2 2

      那么getAndIncrement是如何实现原子操作的呢?让我们一起分析其实现原理,getAndIncrement的源码如下:

     1 public final int getAndIncrement() {
     2     for (;;) {
     3         //先取得AtomicInteger里存储的数值
     4         int current = get();
     5         //对AtomicInteger的当前数值进行加1操作
     6         int next = current + 1;
     7         if (compareAndSet(current, next))
     8             return current;
     9     }
    10 }
    11 public final boolean compareAndSet(int expect, int update) {
    12     return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    13 } 

      源码中for循环体的第一步先取得Atomic Integer里存储的数值,第二步对Atomic Integer的当 前数值进行加1操作,关键的第三步调用compareAndSet方法来进行原子更新操作,该方法先检 查当前数值是否等于current, 等于意味着Atomic Integer的值没有被其他线程修改过,则将

      Atomic Integer的当前数值更新成next的值,如果不等compareAndSet方法会返回false, 程序会进 入for循环重新进行compareAndSet操作。

      Atomic包提供了3种基本类型的原子更新,但是Java的基本类型里还有char、float和double等。那么问题来了,如何原子的更新其他的基本类型呢? Atomic包里的类基本都是使用Unsafe 实现的,让我们一起看一下Unsafe的源码,如代码清单

     1 /***
     2  *  如果当前数值时expected,则原子的将java变量更新成x
     3  *    @return 如果更新成功则返回true
     4  */
     5 public final class Unsafe {
     6     ......
     7 
     8     public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
     9 
    10     public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
    11 
    12     public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
    13 
    14     ......
    15 }

      通过代码,我们发现Unsafe只提供了3种CAS方法: compareAndSwapObj ect、compareAndSwaplnt和compareAndSwapLong, 再看AtomicBoolean原码,发现它是先把Boolean转换成整 型,再使用compareAndSwaplnt进行CAS, 所以原子更新char、float和clouble变量也可以用类似 的思路来实现。

    三、原子更新数组

      通过原子的方式更新数组里的某个元素,Atomic包提供了以下4个类。

    • AtomicIntegerArray:原子更新整型数组里的元素

    • AtomicLongArray:原子更新长整型数组里的元素

    • AtomicReferenceArray:原子更新引用类型数组里的元素

      AtomicIntegerArray主要提供原子的方式更新数组里的整型,常用方法如下:

      • int addAndGet(int i, int delta): 以原子方式将输入值与数组中索引i的元素相加。

      • boolean compareAndSet(int i, int expect, int update): 如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

      以上几个类提供的方法几乎一样,所以本节仅以Atomic Integer Array为例进行讲解, AtomiclntegerArray的使用实例代码如代码清单

    示例:

     1 public class AtomicIntegerArrayTest {
     2     public static void main(String[] args) {
     3 
     4         int[] arr = new int[]{1, 2, 3};
     5         AtomicIntegerArray aia = new AtomicIntegerArray(arr);
     6         aia.getAndSet(0, 3);
     7         System.out.println(aia.get(0));
     8         System.out.println(arr[0]);
     9     }
    10 }

      输出:

    1 3
    2 1

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

    四、原子更新引用类型

      原子更新基本类型的Atomiclnteger, 只能更新一个变量,如果要原子更新多个变量,就需 要使用这个原子更新引用类型提供的类。Atomic包提供了以下3个类。

    • AtomicReference: 原子更新引用类型。

    • AtomicReferenceFieldUpdater: 原子更新引用类型里的字段。

    • AtomicMarkableReference: 原子更新带有标记位的引用类型。可以原子更新一个布尔类 型的标记位和引用类型。构造方法是AtomicMarkableReference (V initialRef, boolean initialMark)。

      以上几个类提供的方法几乎一样,所以本节仅以AtomicReference为例进行讲解, AtomicReference的使用示例代码如代码清单

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

      代码中首先构建一个user对象,然后把user对象设置进AtomicReferenc中,最后调用 compareAndSet方法进行原子更新橾作,实现原理同Atomiclnteger里的compareAndSet方法。代 码执行后输出结果如下。

    1 Shinichi
    2 17

    五、原子更新字段类

      如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供 了以下3个类进行原子字段更新。

    • AtomiclntegerFieldUpdater: 原子更新整型的字段的更新器。

    • AtomicLongFieldUpdater: 原子更新长整型字段的更新器。

    • AtomicStampedReference: 原子更新带有版本号的引用类型。该类将整数值与引用关联起 来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的 ABA问题。

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

      以上3个类提供的方法几乎一样,所以本节仅以AstomiclntegerFieldUpdater为例进行讲解, AstomiclntegerFieldUpdater的示例代码如代码清单

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

    代码执行结果输出如下:

    1 10
    2 11  

    参考:

      1、《Java并发编程的艺术》

  • 相关阅读:
    HDU3336 Count the string —— KMP next数组
    CodeForces
    51Nod 1627 瞬间移动 —— 组合数学
    51Nod 1158 全是1的最大子矩阵 —— 预处理 + 暴力枚举 or 单调栈
    51Nod 1225 余数之和 —— 分区枚举
    51Nod 1084 矩阵取数问题 V2 —— 最小费用最大流 or 多线程DP
    51Nod 机器人走方格 V3 —— 卡特兰数、Lucas定理
    51Nod XOR key —— 区间最大异或值 可持久化字典树
    HDU4825 Xor Sum —— Trie树
    51Nod 1515 明辨是非 —— 并查集 + 启发式合并
  • 原文地址:https://www.cnblogs.com/h--d/p/14570700.html
Copyright © 2011-2022 走看看