1 Star 0 Fork 0

cxylk / Java-Notes

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
原子类.md 22.93 KB
一键复制 编辑 原始数据 按行查看 历史
cxylk 提交于 2020-12-24 14:38 . 上传图片到路过图床

简介

Java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了

一种用法简单、性能高效、线程安全地更新一个变量的方式,在JDK1.8中,有17个类

这些类的大概用途:

  • 原子更新基本类型
  • 原子更新数组
  • 原子更新引用
  • 原子更新字段(属性)

原子更新基本类型

Atomic提供了三个类

  • AtomicInteger:原子更新整型
  • AtomicLong:原子更新长整型
  • AtomicBoolean:原子更新布尔类型

首先看AtomicInteger类。

下面看看getAndIncrement()方法

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

可以看到,底层还是调用了Unsafe的getAndAddInt方法,继续看getAndAddInt方法

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!CompareAndSwapInt(o, offset, v, v + delta));
    return v;
}

因为CAS是“无锁”的基础,它允许更新失败。所以经常会与while循环搭配,在失败后不断去重试。

v是要返回的值,即该方法返回的是原来的值,而新值是v+delta。这里使用了do-while循环,它的目的是保证循环体内的语句至少会被执行一遍。这样才能保证return的值v是我们期望的值。至于为什么要把获取“旧值”的操作放到循环体内呢?因为CAS如果旧值V不等于预期值E,它就会更新失败,说明旧值发生了变化。那我们当然需要返回的是被其他线程改变之后的值,因此放到了do循环体内。

下面看一个具体例子

package com.cxylk.thread.atomic;

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Classname AtomicIntegerDemo
 * @Description TODO
 * @Author likui
 * @Date 2020/12/6 22:42
 **/
public class AtomicIntegerDemo {
    private static final Unsafe unsafe;

    private static final long stateOffset;

    private volatile long state=1;

    private static AtomicInteger integer=new AtomicInteger();

    static {
        Field field= null;
        try {
            field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe= (Unsafe) field.get(null);
            stateOffset=unsafe.objectFieldOffset(AtomicIntegerDemo.class.getDeclaredField("state"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    public static void main(String[] args) {
//        System.out.println(integer.incrementAndGet());
        //这里调用到的Unsafe中getAndAddInt方法中的原始值是AtomicInteger中的value
//        System.out.println("返回旧值"+integer.getAndIncrement());//输出0,先get再increment
//        System.out.println("获取当前值"+integer.get());//这时候的值就是加1后的值

        System.out.println("========================");

        System.out.println("返回新值"+integer.incrementAndGet());//返回加1后的值
        System.out.println("获取当前值"+integer.get());//这时候获取的值就是返回的值
        //这里传过去的原始值就是该类中定义的state
        int result=get(new AtomicIntegerDemo(),stateOffset,1);
        System.out.println(result);
    }

    /**
     * 实现Unsafe类中的getAndAddInt方法,获取对象obj中偏移量为offset的变量对应volatile语义的当前值,
     * 并设置变量值为原始值+addValue
     * @param obj 所要操作的对象
     * @param offset 在对象中的偏移地址
     * @param addValue 增量
     * @return 注意,这里返回的是当前变量的值,不是原始值+addValue
     */
    public static int get(Object obj,long offset,int addValue){
        int var5;
        do{
            //在该类中,这里获取的就是state值,所以,state初始值大小决定了这里的变量值
            //也就是说,这里的变量值就是原始变量值
            var5=unsafe.getIntVolatile(obj,offset);
        }while (!unsafe.compareAndSwapInt(obj,offset,var5,addValue+var5));
        return var5;
    }
}

AtomicLong的基本用法

package com.cxylk.thread.atomic;

import java.util.concurrent.atomic.AtomicLong;

/**
 * @Classname AtomicLongDemo
 * @Description 通过一个多线程统计0的个数加深对AtomicLong原子变量操作类的理解
 * @Author likui
 * @Date 2020/12/6 21:37
 **/
public class AtomicLongDemo {
    //创建Long型原子计数器
    private static AtomicLong atomicLong=new AtomicLong();
    //创建数组存放数据
    private static Integer[] arrayOne=new Integer[]{0,1,2,3,0,5,6,0,56,0};
    private static Integer[] arrayTwo=new Integer[]{10,1,2,3,0,5,6,0,45,0};

    public static void main(String[] args) throws InterruptedException {
        //线程one统计arrayOne中0的个数
        Thread threadOne=new Thread(()->{
            int size=arrayOne.length;
            for (int i = 0; i < size; i++) {
                if(arrayOne[i].intValue() ==0){
                    //自增后的值,从名字上也可以看出,先increment,再get
                    atomicLong.incrementAndGet();
                }
            }
        },"线程one");

        //线程two统计arrayTwo中0的个数
        Thread threadTwo=new Thread(()->{
            int size=arrayTwo.length;
            for (int i = 0; i < size; i++) {
                if(arrayTwo[i].intValue()==0){
                    atomicLong.incrementAndGet();
                }
            }
        },"线程two");

        //启动子线程
        threadOne.start();
        threadTwo.start();

        //等待线程执行完毕,这里一定要加上这步,防止main线程不等子线程执行完就返回,0的个数将是0
        threadOne.join();
        threadTwo.join();

        System.out.println("0的个数:"+atomicLong.get());
    }
}

原子更新数组

通过原子方式更新数组里的某个元素,主要有4个类

  • AtomicIntegerArray 原子更新整型数组里的元素
  • AtomicLongArray 原子更新长整型数组里的元素
  • AtomicReferenceArray 原子更新引用类型数组里的元素

几个类的方法大同小异,主要学习AtomicIntegerArray的常用方法

先看看构造函数

    private final int[] array;

	/**
     * Creates a new AtomicIntegerArray of the given length, with all
     * elements initially zero.
     *
     * @param length the length of the array
     */
    public AtomicIntegerArray(int length) {
        array = new int[length];
    }

 	/**
     * Creates a new AtomicIntegerArray with the same length as, and
     * all elements copied from, the given array.
     *
     * @param array the array to copy elements from
     * @throws NullPointerException if array is null
     */
    public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }

两个构造方法:

  • 第一个传入一个数后,根据当前数作为数组大小创建一个新的数组
  • 第二个是传入一个数组,将当前的数组复制一份,这样当AtomicIntegerArray对内部元素进行修改时,不会影响原来的数组

具体例子

package com.cxylk.thread.atomic;

import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * @Classname AtomicIntegerArrayDemo
 * @Description 原子更新整型数组里的元素
 * @Author likui
 * @Date 2020/12/7 21:37
 **/
public class AtomicIntegerArrayDemo {
    private static int[] value=new int[]{1,2,3};
    //当构造函数传入值的时候,AtomicIntegerArray会将当前值复制一份 this.array=value.clone()
    private static AtomicIntegerArray ai=new AtomicIntegerArray(value);

    public static void main(String[] args) {
        System.out.println(ai.getAndSet(0, 3));//输入的是原始值
        System.out.println(ai.get(0));//这时候索引0的值变为了3
        //上面说了当前传进去的value被复制了一份,所以当AtomicIntegerArray
        //对内部元素进行修改时,不会影响传入的数组
        System.out.println(value[0]);//还是1
    }
}

其中的getAndSet源码如下

 /**
     * 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 int getAndSet(int i, int newValue) {
        return unsafe.getAndSetInt(array, checkedByteOffset(i), newValue);
    }

可以看到,内部还是调用了Unsafe的getAndSetInt方法,其中checkdByteOffset(i)对参数进行校验

    public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5;
    }

原子更新引用类型

原子更新基本类型只能更新单个变量,当要对多个变量进行原子更新时就要使用这个原子更新引用类型提供的类。Atomic提供以下3个类

  • AtomicReference:原子更新引用类型
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段(抽象类)
  • AtomicMarkeableReference:原子更新带有标记位的引用类型

主要看一下AtomicReference类的使用

看下该类中的字段和构造方法及一些常用方法

  	//value在该类中的偏移量
	private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

	//valatile保证可见性
    private volatile V value;

	   /**
     * Creates a new AtomicReference with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicReference(V initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicReference with null initial value.
     */
    public AtomicReference() {
    }

package com.cxylk.thread.atomic;

import java.util.concurrent.atomic.AtomicReference;

/**
 * @Classname AtomicReferenceDemo
 * @Description 原子更新引用类型。原子更新基本类型AtomicInteger只能更新一个变量,如果
 *              需要原子更新多个变量,就需要使用原子更新应用类型提供的这个类
 * @Author likui
 * @Date 2020/12/7 22:37
 **/
public class AtomicReferenceDemo {
    //默认构造函数,此时AtomicReference中的User value=null
    static AtomicReference<User> atomicReference=new AtomicReference<User>();

    public static void main(String[] args) {
        User user=new User("likui","20");
        //此时value=user
        atomicReference.set(user);
        User newUser=new User("cxylk","22");
        //更新值,更新之后的value=newUser
        atomicReference.compareAndSet(user,newUser);
        //返回更新后的值,即newUser
        System.out.println(atomicReference.get().getName());
        System.out.println(atomicReference.get().getAge());
    }

    static class User{
        private String name;

        private String age;

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

        public String getName() {
            return name;
        }

        public String getAge() {
            return age;
        }
    }
}

输出结果cxylk,22

原子更新字段类

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

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器(抽象类)
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器(抽象类)
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号。解决使用CAS进行原子更新可鞥出现的ABA问题

下面以AtomicIntegerFieldUpdater进行演示

先来看看该类源码

public abstract class AtomicIntegerFieldUpdater<T> {
    @CallerSensitive
    public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
                                                              String fieldName) {
        return new AtomicIntegerFieldUpdaterImpl<U>
            (tclass, fieldName, Reflection.getCallerClass());
    }
    .....
        
    private static final class AtomicIntegerFieldUpdaterImpl<T>
        extends AtomicIntegerFieldUpdater<T> {
        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        private final long offset;
        /**
         * if field is protected, the subclass constructing updater, else
         * the same as tclass
         */
        private final Class<?> cclass;
        /** class holding the field */
        private final Class<T> tclass;

        AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
                                      final String fieldName,
                                      final Class<?> caller) {
            final Field field;
            final int modifiers;
            try {
                field = AccessController.doPrivileged(
                    new PrivilegedExceptionAction<Field>() {
                        public Field run() throws NoSuchFieldException {
                            return tclass.getDeclaredField(fieldName);
                        }
                    });
                modifiers = field.getModifiers();
                sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                    caller, tclass, null, modifiers);
                ClassLoader cl = tclass.getClassLoader();
                ClassLoader ccl = caller.getClassLoader();
                if ((ccl != null) && (ccl != cl) &&
                    ((cl == null) || !isAncestor(cl, ccl))) {
                    sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                }
            } catch (PrivilegedActionException pae) {
                throw new RuntimeException(pae.getException());
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }

            if (field.getType() != int.class)
                throw new IllegalArgumentException("Must be integer type");

            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

            // Access to protected field members is restricted to receivers only
            // of the accessing class, or one of its subclasses, and the
            // accessing class must in turn be a subclass (or package sibling)
            // of the protected member's defining class.
            // If the updater refers to a protected field of a declaring class
            // outside the current package, the receiver argument will be
            // narrowed to the type of the accessing class.
            this.cclass = (Modifier.isProtected(modifiers) &&
                           tclass.isAssignableFrom(caller) &&
                           !isSamePackage(tclass, caller))
                          ? caller : tclass;
            this.tclass = tclass;
            this.offset = U.objectFieldOffset(field);
        }
        .....

首先可以看到该类是抽象类,所以外部类想要获取到该类的实例,每次使用的时候必须使用静态方法newUpdater()创建一个更新器i。而且由于有些类型的字段是不可被更新的,所以被更新的字段有很多要求,不满足要求的话会抛出各种异常。下面具体探讨

package com.cxylk.thread.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * @Classname AtomicIntegerFieldUpdaterDemo
 * @Description 原子更新字段类AtomicIntegerFieldUpdater测试
 * @Author likui
 * @Date 2020/12/8 9:39
 **/
public class AtomicIntegerFieldUpdaterDemo {
    static AtomicIntegerFieldUpdater<User> aifu=AtomicIntegerFieldUpdater.newUpdater(User.class,"age");

    public static void main(String[] args) {
        User user=new User("lk","20");
        //得到的是原始值
        System.out.println(aifu.getAndIncrement(user));
        //获取更新后的值
        System.out.println(aifu.get(user));
    }


    static class User{
        private String name;

        private String age;

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

        public String getName() {
            return name;
        }

        public String getAge() {
            return age;
        }
    }
}

上面例子中,我想要更新User类中的age字段,让它加1,但是程序运行时将会抛出异常

Caused by: java.lang.RuntimeException: java.lang.IllegalAccessException: Class com.cxylk.thread.atomic.AtomicIntegerFieldUpdaterDemo can not access a member of class com.cxylk.thread.atomic.AtomicIntegerFieldUpdaterDemo$User with modifiers "private"
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:405)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:88)
	at com.cxylk.thread.atomic.AtomicIntegerFieldUpdaterDemo.<clinit>(AtomicIntegerFieldUpdaterDemo.java:12)
Caused by: java.lang.IllegalAccessException: Class com.cxylk.thread.atomic.AtomicIntegerFieldUpdaterDemo can not access a member of class com.cxylk.thread.atomic.AtomicIntegerFieldUpdaterDemo$User with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
	at sun.reflect.misc.ReflectUtil.ensureMemberAccess(ReflectUtil.java:103)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:394)
	... 2 more
Exception in thread "main" 

1.大概意思就是当前类不能访问被private修饰的age字段

对应源码中的静态内部类

field = AccessController.doPrivileged(
    new PrivilegedExceptionAction<Field>() {
        public Field run() throws NoSuchFieldException {
            return tclass.getDeclaredField(fieldName);
        }
    });
modifiers = field.getModifiers();

其中field返回一个age的全名,modifiers得到该字段修饰符代表的具体数值,各修饰符对应的具体数值如下

PUBLIC: 1
PRIVATE: 2
PROTECTED: 4
STATIC: 8
FINAL: 16
SYNCHRONIZED: 32
VOLATILE: 64
TRANSIENT: 128
NATIVE: 256
INTERFACE: 512
ABSTRACT: 1024
STRICT: 2048

如果是public volatile,那么modifiers=65

所以这里的modifiers=2

sun.reflect.misc.ReflectUtil.ensureMemberAccess(
    caller, tclass, null, modifiers);
ClassLoader cl = tclass.getClassLoader();
ClassLoader ccl = caller.getClassLoader();
if ((ccl != null) && (ccl != cl) &&
    ((cl == null) || !isAncestor(cl, ccl))) {
    sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
}
} catch (PrivilegedActionException pae) {
    throw new RuntimeException(pae.getException());
} catch (Exception ex) {
    throw new RuntimeException(ex);
}

上面进行一个访问权限的判定,这里不做研究,最终抛出上面的异常

2.将age改为public修饰

public String age;

运行程序,报错

Caused by: java.lang.IllegalArgumentException: Must be integer type
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:409)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:88)
	at com.cxylk.thread.atomic.AtomicIntegerFieldUpdaterDemo.<clinit>(AtomicIntegerFieldUpdaterDemo.java:12)
Exception in thread "main" 

可以看到,类型必须时integer类型

源码中对应位置

            if (field.getType() != int.class)
                throw new IllegalArgumentException("Must be integer type");

那么将其改为Integer类型,仍然抛出上述异常。原因上面源码说的很清除了,只能修改ing类型的字段,不能修改其包装类型,如果要修改其包装类型,使用AtomicReferenceFieldUpdater。对于AtomicLongFieldUpdater也是一样的。

3.将其改为int 类型,运行程序,报错

Caused by: java.lang.IllegalArgumentException: Must be volatile type
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:412)
	at java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:88)
	at com.cxylk.thread.atomic.AtomicIntegerFieldUpdaterDemo.<clinit>(AtomicIntegerFieldUpdaterDemo.java:12)

4.说改字段必须时volatile类型的,对应源码位置

            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

改为volatile修饰

public volatile int age;

运行结果20 21

5.如果给字段加static关键字也会报错

Caused by: java.lang.IllegalArgumentException
	at sun.misc.Unsafe.objectFieldOffset(Native Method)

原因就是objectFieldOffset获取的改字段在对象中的偏移地址,而static修饰的变量属于类变量

最后,做个总结:

  • 必须是volatile类型
  • 必须使用public修饰,调用者与操作对象的字段关系一致
  • 只能是实例变量,不能是类变量
  • 只能是可修改变量,不能使用final修饰,其实final和volatile语义上冲突
  • 只能修改int类型,不能修改其包装类型

上面说了AtomicInterFileldUpdater只能更新int类型的字段,下面用AtominReferenceFieldUpdater更新Integer字段

package com.cxylk.thread.atomic;

import sun.misc.Unsafe;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

/**
 * @Classname AtomicReferenceFieldUpdaterDemo
 * @Description 原子更新引用类型中的字段,解决AtomicIntegerFieldUpdater或者LongUpdater只能更新
 *              int和long类型字段的问题
 * @Author likui
 * @Date 2020/12/8 11:02
 **/
public class AtomicReferenceFieldUpdaterDemo {
    //第一个值是要更新字段所再的类,第二个是该字段的类型,第三个是字段名
    private static AtomicReferenceFieldUpdater<User,Integer> arfu=
            AtomicReferenceFieldUpdater.newUpdater(User.class,Integer.class,"age");

    public static void main(String[] args) {
        User user=new User("likui",20);
        User newUser=new User("cxylk",22);
        arfu.compareAndSet(user,user.age,newUser.age);
        System.out.println(user.age);
    }
    static class User{
        private String name;

        public volatile Integer age;

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

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

用法基本一样,注意字段仍然是public volatile,构造函数传值也有点区别

1
https://gitee.com/cxylk/Java-Notes.git
git@gitee.com:cxylk/Java-Notes.git
cxylk
Java-Notes
Java-Notes
main

搜索帮助