图灵学院:Java高并发之Atomic

concurrent   atomic




一:原子操作


    原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为“不可被中断的一个或一系列操作” 

原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch



二:CAS


   CAS:Compare and Swap, 翻译成比较并交换。

   java.util.concurrent包中借助CAS实现了区别于synchronized同步锁的一种乐观锁

   CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。

   而compareAndSwapInt就是借助C来调用CPU底层指令实现的。

   CAS并不是无阻塞,只是阻塞并非在语言、线程方面,而是在硬件层面,所以无疑这样的操作会更快更高效!

   下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码:

   public final native boolean compareAndSwapInt(Object o, long offset,

                                                 int expected,

                                                 int x);

 

三:Volatile(最好先了解下JMM内存模型)


Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。

 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

  举个列子:以双CPU为例a、bCPU,现在有A线程 B线程,共享(全局)变量i  其中A线程在aCPU执行,B线程在bCPU执行,如果变量i使用了volatile那么不管是A 线程还是B线程将I的值变化后立马会写到主内存当中,如果不用volatile,那么i的值会存在cpu的笨蛋内存中,并不会马上写入我们的主内存。

具体什么时候刷新共享数据到主内存是不确定的




四:java原子操作


    java的并发包java.util.concurrent.atomic.* 提供了原子操作的相关类,其中类可以分成4组

    标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

    数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

    更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

    复合变量类:AtomicMarkableReference,AtomicStampedReference

    

    

    1:标量类 AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference


    这四种基本类型用来处理布尔,整数,长整数,对象四种数据。

     构造函数(两个构造函数)

     默认的构造函数:初始化的数据分别是false,0,0,null

        带参构造函数:参数为初始化的数据

    set( )和get( )方法:可以原子地设定和获取atomic的数据。类似于volatile,保证数据会在主存中设置或读取

    

    getAndSet( )方法

    原子的将变量设定为新数据,同时返回先前的旧数据

    其本质是get( )操作,然后做set( )操作。

       尽管这2个操作都是atomic,但是他们合并在一起的时候,就不是atomic。在Java的源程序的级别上,如果不依赖synchronized的机制来完成这个工作,是不可能的。只有依靠native方法才可以。

    

    compareAndSet( ) 和weakCompareAndSet( )方法

    这两个方法都是conditional modifier方法。这2个方法接受2个参数,一个是期望数据(expected),一个是新数据(new);如果atomic里面的数据和期望数据一致,则将新数据设定给atomic的数据,返回true,表明成功;否则就不设定,并返回false。

    

    对于AtomicInteger、AtomicLong还提供了一些特别的方法。getAndIncrement( )、incrementAndGet( )、getAndDecrement( )、decrementAndGet ( )、addAndGet( )、getAndAdd( )以实现一些加法,减法原子操作。(注意 --i、++i不是原子操作,其中包含有3个操作步骤:第一步,读取i;第二步,加1或减1;第三步:写回内存)

    代码例子

public class AtomicIntegerTest extends Thread {
        public static AtomicInteger astom_i = new AtomicInteger(100);
        public void run() {
             //System.out.println(Thread.currentThread().getName() + ":incrementAndGet:" + (astom_i.incrementAndGet()));
              System.out.println(Thread.currentThread().getName() + ":incrementAndGet:" + (astom_i.getAndIncrement()));
            // System.out.println(Thread.currentThread().getName() + ":getAndDecrement:" + (astom_i.getAndDecrement()));
            // System.out.println(Thread.currentThread().getName() + ":getAndDecrement:" + (astom_i.decrementAndGet()));
        }
        public static void main(String[] args) {
            AtomicIntegerTest counter = new AtomicIntegerTest();
            Thread t1 = new Thread(counter);
            Thread t2 = new Thread(counter);
            Thread t3 = new Thread(counter);
            Thread t4 = new Thread(counter);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    

    

    

public class AtomicReferenceTest {
    public static AtomicReference atomicUserRef = new AtomicReference();
    public static void main(String[] args) {
    User user = new User("conan", 15);
    atomicUserRef.set(user);
    User updateUser = new User("Shinichi", 17);
    atomicUserRef.compareAndSet(user, updateUser);
    System.out.println(atomicUserRef.get().getName());
    System.out.println(atomicUserRef.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;
    }
    }
    }

     

2数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray


          分别表示整数数组、long型数组和普通的对象数组

    

    下面给出一个简单的示例,展示AtomicIntegerArray使用:

 public class AtomicIntegerArrayDemo {
        static AtomicIntegerArray arr = new AtomicIntegerArray(10);
        public static void main(String[] args) throws InterruptedException {
            Thread[] ts = new Thread[10];
            for (int k = 0; k < 10; k++) {
                ts[k] = new Thread(new AddThread());
            }
            for (int k = 0; k < 10; k++) {
                ts[k].start();
            }
            for (int k = 0; k < 10; k++) {
                ts[k].join();
            }
            System.out.println(arr);
        }
    }
    class AddThread implements Runnable {
        public void run() {
            for (int k = 0; k < 100; k++)
                AtomicIntegerArrayDemo.arr.getAndIncrement(k % AtomicIntegerArrayDemo.arr.length());
        }
    }
     运行结果:[100, 100, 100, 100, 100, 100, 100, 100, 100, 100]

上述代码第2行,申明了一个内含10个元素的数组。第3行定义的线程对数组内10个元素进行累加操作,每个元素各加100次。第11行,开启10个这样的线程。因此,可以预测,如果线程安全,数组内10个元素的值必然都是100。反之,如果线程不安全,则部分或者全部数值会小于100。

这说明AtomicIntegerArray确实合理地保证了数组的线程安全性。


    3: 更新器类 AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater


    基于于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。API非常简单,但是也是有一些约束:

    (1)字段必须是volatile类型的

    (2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说 调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

    (3)只能是实例变量,不能是类变量,也就是说不能加static关键字。

    (4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。

    (5)对于AtomicIntegerFieldUpdater 和AtomicLongFieldUpdater 只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater

    

    代码:

public class AtomicIntegerFieldUpdaterTest {
    private static AtomicIntegerFieldUpdater a = AtomicIntegerFieldUpdater
    .newUpdater(User.class, "old");
    public static void main(String[] args) {
    User conan = new User("conan", 10);
    System.out.println(a.getAndIncrement(conan));
    System.out.println(a.get(conan));
    }
    public static class User {
    private String name;
    public volatile int old;
    public User(String name, int old) {
    this.name = name;
    this.old = old;
    }
    public String getName() {
    return name;
    }
    public int getOld() {
    return old;
    }
    }
    }

猜你喜欢

转载自blog.csdn.net/qq_42135428/article/details/80308538