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

 

posted on 2021-11-17 16:37  homle  阅读(416)  评论(0编辑  收藏  举报