• Java 原子类介绍


    1. 原子类引入

      先看一个i++的问题

    public class AtomicTest01 {
        public static int i = 0;
        public static void main(String[] args) {
            Runnable task = new Runnable(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.print(i++ + " ");
                }
            };
            for (int j = 0; j < 10; j++) {
                Thread thread = new Thread(task);
                thread.start();
            }
        }
    }
    //运行结果
    2 0 4 1 3 0 1 7 6 5 

      在程序中当我们对变量值++时,我们期望的结果是输出0~9,但是输出的结果并不是,这时因为当多个线程更新整个变量时,可能存在A线程更新了i+1,B线程也更新了i+1,经过两个线程操作后可能i的值不等于3,而是等于2,因为A和B线程在更新变量i时拿到的初始值都是1,这就是线程不安全的更新操作,这时我们可以用同步锁来解决这个问题,synchronized可以保证多线程不会同时更新变量。

    public class AtomicTest01 {
        public static int i = 0;
        public static void main(String[] args) {
            Runnable task = new Runnable(){
                @Override
                public void run() {
                    synchronized (this){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.print(i++ + " ");
                    }
                }
            };
            for (int j = 0; j < 10; j++) {
                Thread thread = new Thread(task);
                thread.start();
            }
        }
    }
    //运行结果
    0 1 2 3 4 5 6 7 8 9

      当给i++加同步锁后,可以解决线程更新的安全问题。

      然而Java从JDK1.5开始提供了java.util.concurrent.atomic包,这个包中的原子操作类提供了更简单,高效,线程安全的更新变量的方式。

    2. 原子类的分类

      基本类型:AtomicInteger,AtomicLong,AtomicBoolean;

      数组类型:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray;

      引用类型:AtomicReference,AtomicStampedReference,AtomicMarkableReferce;

      对象属性修改类型:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater;

    3. 原子更新基本类型

      AtomicInteger:原子更新整型;AtomicLong:原子更新长整型;AtomicBoolean原子更新布尔类型;这3个类提供的方法几乎一样。

      AtomicInteger中常用方法:

    final int getAndIncrement()    //以原子方式将当前值加1,并返回加1前的值,相当于i++

      所以可以使用原子类解决刚才的i++问题

    public class AtomicTest02 {
        public static AtomicInteger atomicInteger = new AtomicInteger(0);
        public static void main(String[] args) {
            Runnable task = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.print(atomicInteger.getAndIncrement() + " ");
                }
            };
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(task);
                thread.start();
            }
        }
    }
    //运行结果
    3 2 6 1 4 7 9 8 0 5 

      查看getAndIncrement()的源码,实际是调用Unsafe类中的getAndAddInt(),而在这个方法中也是使用了CAS方法。

        public final int getAndIncrement() {
            return U.getAndAddInt(this, VALUE, 1);
        }
        public final int getAndAddInt(Object o, long offset, int delta) {
            int v;
            do {
                v = getIntVolatile(o, offset);
            } while (!weakCompareAndSetInt(o, offset, v, v + delta));
            return v;
        }

      CAS(比较交换)

      CAS(V,E,N),V表示要更新的变量,E表示当前期望的变量,N表示更新的值,比较当前值和期望值是否相等,如果相等,则更新为新的值,否则再次循环。

      优点:原子类中也是使用了CAS来实现了线程安全,这种无锁的方式比基于锁的实现性能更优越,使用锁会带来线程间频繁调度的开销。

      缺点:CAS的其他问题:ABA问题和自旋问题。

        ABA问题:线程获取到A时,已经存在其他线程把A改成了B,然后又改成了A,这样线程在获取A时,认为A也没有其他线程对A修改也会更新A。

            对于ABA问题Java原子类中提供了带有标记的引用类“AtomicStampedReference”,通过使用控制变量值的版本来保证CAS的正确性。

        自旋问题:一直循环判断直到修改成功为止,长时间的自旋,多个线程都在自旋,影响性能。

    4. 原子更新数组类型

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

      AtomicIntegerArray常用方法:

    public class AtomicTest03 {
        public static void main(String[] args) {
            int[] array = new int[]{1,2,3,4,5};
            AtomicIntegerArray aia = new AtomicIntegerArray(array);
            aia.set(0,6);
            for (int i = 0; i < aia.length(); i++) {
                System.out.print(aia.get(i) + " ");
            }
            System.out.println();
            System.out.println("aia.getAndIncrement(0): " + aia.getAndIncrement(0));  //6
            System.out.println("aia.incrementAndGet(1): " + aia.incrementAndGet(1));  //3
            System.out.println("aia.getAndDecrement(2): " + aia.getAndDecrement(2));  //3
            System.out.println("aia.decrementAndGet(3): " + aia.decrementAndGet(3));  //3
    
            System.out.println("addAndGet(100): " + aia.addAndGet(0,100));  //107
            System.out.println("getAndAdd(100): " + aia.getAndAdd(1,100));  //3
            System.out.println("get(1): " + aia.get(1));  //103
    
            System.out.println("compareAndSet(): " + aia.compareAndSet(4,5,200));  //200
            System.out.println("get(3): " + aia.get(4));  //200
            for (int i = 0; i < array.length; i++) {
                System.out.print(array[i] + " ");
            }
        }
    }

      数组aia是通过构造方法传递的,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响出入的数组,最后输出还是原来数组中的元素。

    5. 原子更新引用类型

      原子更新基本类型只能更新一个变量,如果要更新多个变量,就需要用原子更新引用类型提供的类。

      AtomicReference:原子更新引用类型 

      AtomicReference常用方法

    public class AtomicTest04 {
        public static AtomicReference<User> ar = new AtomicReference<>();
        public static void main(String[] args) {
            User u1 = new User("connan",15);
            ar.set(u1);
            User u2 = new User("lan",18);
            ar.compareAndSet(u1,u2);
            System.out.println(ar.get().getName());
            System.out.println(ar.get().getOld());
        }
    
        static class User{
            private String name;
            private int old;
            public User(String name, int old){
                this.name = name;
                this.old = old;
            }
    
            public String getName() {
                return name;
            }
    
            public int getOld() {
                return old;
            }
        }
    }

    6. 原子更新字段类

      如果需要原子地更新某个类里的某个字段时,就需要使用原子更新字段类。

      原子更新对象字段AtomicIntegerFieldUpdater (1)先使用静态方法newUpdater()创建一个更新器,并设置要更新的类和属性;(2)更新类的字段(属性)需要使用volatile修饰;

    public class AtomicTest05 {
        public static AtomicIntegerFieldUpdater aifu = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
        public static void main(String[] args) {
            User conan = new User("conan",10);
            System.out.println(aifu.getAndIncrement(conan));  //10
            System.out.println(aifu.get(conan));  //11
        }
    
        static class User{
            private String name;
            public volatile int age;
    
            public User(String name, int age) {
                this.name = name;
                this.age = age;
            }
    
            public String getName() {
                return name;
            }
    
            public int getAge() {
                return age;
            }
        }
    }
  • 相关阅读:
    OpenCV---在图片上加入文字
    DosBox 报错 this program requires dosxnt.exe to be in your path
    iOS开发-UITableView单选多选/复选实现1
    LeetCode第七题,Reverse Integer
    【PostgreSQL】PostgreSQL操作-psql基本命令
    Bootstrap的js插件之弹出框(popover)
    Qt Quick 图像处理实例之美图秀秀(附源代码下载)
    【甘道夫】并行化频繁模式挖掘算法FP Growth及其在Mahout下的命令使用
    用Visual Studio高版本号打开低版本号的project,转换时出现错误:fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
    如何安装ArchLinux
  • 原文地址:https://www.cnblogs.com/homle/p/15561427.html
Copyright © 2020-2023  润新知