java中的原子操作类

什么是原子操作?

“原子操作(atomic operation)是不需要synchronized”,这是多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

在 java中怎样实现原子操作?

在多线程变成的时候我们经常会需要多个线程对同一个变量(资源)进行读写操作,这要就会引发一个线程是否安全(操作的原子性)的问题,通常才去的措施是通过synchronized关键字来实现,但是在java1.5之后java为我们提供了一系列的原子操作类来实现原子操作。
java中原子操作的实现原理:
在Java中利用了现代处理器都支持的CAS(compare and swap)的指令,循环这个指令,直到成功为止。
CAS(compare and swap):比较并且交换。

java中的原子作类:

  1. 更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong
  2. 更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  3. 更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
  4. 原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater

AtomicInteger的使用:

下面通过一个实例来介绍如何使用AtomicInteger。
在这段代码中我们初始化了一个大小为5000的集合,模拟一个Excel导入功能,假设现有5000条数据需要导入,每一条数的插入大概需要耗时50到200毫秒,这其中需要实现原子操作的就是一个索引,我们需要保证每个线程操作这个索引的时候不能有其他线程操作,也就是原子性,下面就采用了java提供的atomicInteger 来实现索引的安全递减


/**
 * @author Jenkin
 * @version 1.0
 */
public class TestAtomic {
    static final AtomicInteger allItems = new AtomicInteger(4999);
    static final int IMPORT_MAX_THREAD_MUM = 500;
    public static void main(String[] args){
    final List<Integer> upLoadSalaryList = new ArrayList<Integer>();
        for(int i=0;i<5000;i++){
        upLoadSalaryList.add(i);
    }
    long b = System.currentTimeMillis();
    //定义线程计数器
    final CountDownLatch count = new CountDownLatch(IMPORT_MAX_THREAD_MUM);
    //定义线程池
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(IMPORT_MAX_THREAD_MUM);
            for(int i=0;i<IMPORT_MAX_THREAD_MUM;i++){
        fixedThreadPool.execute(() -> {
            while(true){
                int index= allItems.getAndDecrement();
                if(index<0){
                    break;
                }
                int i1 = upLoadSalaryList.get(index);
                try {
                    /* do something...*/
                    int sleepTime = (int)(Math.random()*200);
                    if(sleepTime<50){
                        sleepTime = (int)(Math.random()*200);
                    }
                    /*let thread sleep 50 - 200 ms to simulate the business code execute*/
                     Thread.currentThread().sleep(sleepTime);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            count.countDown();
        });
    }
        try {
        count.await(15, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
        System.out.println("execute over..."+"   cost time ==>"+(System.currentTimeMillis()-b));
        fixedThreadPool.shutdown();

    }

}

使用原子操作的好处

1、在上面的代码中我们使用原子操作类代替了以往的synchronized关键字,让我们的代码变得更简洁。
2、由于没有锁的参与,对原子变量的操作不会引发死锁。
3、不会忘了在正确的时候获取锁和释放锁 。

CAS存在的问题

java原子操作类的实现是基于CPU的CAS指令来实现的。
CAS的步骤:拿出期望值===>将期望值与
在使用CAS实现的时候却会面临如下几个问题:

1. ABA问题
什么是ABA问题?
因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新
假设有两个线程:线程1和线程2,这两个线程需要执行一个赋值操作,变量X的初始值为A,在这里线程1和线程2 拿到的变量X的值。线程1执行了一串操作,将X变为B,然后又变为A,然后在线程2 处理的时候发现变量X还是A,没有发生变化,所有就又对变量X进行了赋值,实际上这个A已经不是原来的那个A了。
解决这个问题的办法就是使用版本号,在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2. 开销问题
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3. 只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

关于CAS这部分问题参考文章:https://www.cnblogs.com/kisty/p/5408264.html

实现AtomicInteger的递增方法的自旋CAS

class MyAtomicInt{
    private static AtomicInteger ato= new AtomicInteger(0);
    public static void main(String[] args){
        MyAtomicInt myAtomicInt = new MyAtomicInt();
        System.out.println(myAtomicInt.getVal());
        myAtomicInt.increment() ;
        System.out.println(myAtomicInt.getVal());
    }
    public  void increment(){
        int val = ato.get();
        while(!ato.compareAndSet(val,val+1)){

        }
    }
    public  int getVal(){
        return ato.get();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章