AtomicInteger原理

1 AtomicInteger特性
如果要实现自增计数器,如果使用i++实现,由于i++非原子操作,所以多线程下要添加synchronized来实现自增,由于synchronized同步性能比较差,后面会说到。java通过AtomicInteger更轻量的锁来实现多线程下的原子自增。



2 AtomicInteger原理
AtomicInteger使用硬件提供的CAS原语实现原子操作,硬件实现的锁机制,锁粒度更小,性能更好。

3 硬件如何实现原子操作
处理器能够自动保证内存操作的原子性,其中一个处理器访问这个内存地址的时候,其他处理器不允许访问。但是复杂的内存操作,如跨总线、跨多个缓存行或者跨页的访问,不能自动保证原子性。但是处理器通过总线锁和缓存锁两个机制来保证复杂内存操作的原子性。
3.1 总线锁
当其中一个处理器向总线上输出LOCK信号时,其他处理器将被阻塞。
3.2缓存锁
在同一时刻,只需保证对某个内存地址的操作是原子的,但是总线锁把处理器和内存之间的通信阻断了,阻断期间也不可以操作内存中的其他数据,所以总线锁开销比较大,现代处理器通常通过这种方式优化,但是如果跨多个cache line,就只能使用总线锁。
缓存锁就是缓存在处理器缓存行中的内存区域在LOCK期间被锁定,通过缓存一致性机制来保证缓存一致性,缓存一致性机制会阻止同时修改缓存在多个处理器中的内存数据。

4 synchronized 和 AtomicInteger性能比较
synchronized是一种悲观锁,锁存的的问题:
1.多线程竞争的情况下,频繁的加锁解锁导致过多的线程上下文切换。
2.一个线程持有锁,会导致其他请求该锁的线程挂起。
3.如果高优先级线程请求的锁,被低优先级线程占用,则会发生优先级倒置。

性能比较代码:
public class Test {
   private AtomicInteger atomicInteger = new AtomicInteger();
   private int counter ;
   private void casCounter(){
      for(int i=0;i<10;i++){
         atomicInteger.incrementAndGet();
      }
   }
   private synchronized  void synchronizedCouter(){
      for(int i=0;i<10;i++){
         counter ++;
      }
   }

   public static void main(String[] args) throws Exception{
      final int THREAD_NUMBER = 1000;
      final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_NUMBER);
      long beginTime = System.currentTimeMillis();
      final CountDownLatch latch = new CountDownLatch(THREAD_NUMBER);
      final Test test = new Test();
      for(int i=0;i<THREAD_NUMBER;i++){
         executorService.execute(new Thread(new Runnable() {
            public void run() {
               test.synchronizedCouter();
               latch.countDown();
            }

         }));
      }
      latch.await();
      System.out.println("synchronized使用时间;"+(System.currentTimeMillis() - beginTime) +",test.counter"+test.counter);
      beginTime = System.currentTimeMillis();
      final CountDownLatch latch2 = new CountDownLatch(THREAD_NUMBER);
      for(int i=0;i<THREAD_NUMBER;i++){
         executorService.execute(new Thread(new Runnable() {
            public void run() {
               test.casCounter();
               latch2.countDown();
            }

         }));
      }
      latch2.await();
      System.out.println("cas使用时间;"+(System.currentTimeMillis() - beginTime) + ",test.atomicinteger"+test.atomicInteger.get());

   }
}




测试输出:
synchronized使用时间;122,test.counter10000
cas使用时间;29,test.atomicinteger10000

可见性能差距还是很明显的。


5 乐观锁和悲观锁
悲观锁,假设有竞争,第一个线程获取锁时,其他线程请求同一个锁时会阻塞,直到锁被释放。悲观锁认为如果不采取同步措施就会有问题,所以无论是否真的会出现竞争都会加锁,有一定的性能消耗。也称为阻塞同步。
乐观锁,假设没有竞争,如果执行期间出现了竞争,则失败并重新尝试。称为非阻塞同步。
synchronized属于悲观锁,AtomicInteger使用乐观锁。


6 CAS操作
CAS,假设内存值为V,预期值为A,要设置的新值为B,则比较V和A是否相同,如果相同,则将V中的值赋值为B,否则什么都不做。
7 AtomicInteger关键代码
//value类型是volatile类型的,保证内存的可见性
private volatile int value;

//通过这个方法实现自增,通过调用compareAndSet实现CAS操作,如果不成功则不断的尝试
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

//调用Unsafe类实现CAS操作,这是一个native方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
   }



代码相对来说比较简单。

8 ABA问题
什么是ABA问题,如何解决,Java如何解决ABA问题。

CAS操作可能会出现ABA问题,比如预期值为A,如果期间A改变为了B,然后比较之前又被修改为了A,则比较仍然相同,但是有时就不应该执行了。
通常采用同数据库事务相同的原理来解决ABA问题,即添加一个版本号,1A-2B-3A,每次比较的时候也比较版本号,如果版本号不同则视为已经不同,拒绝执行。
java通过AtomicMarkableReference和AtomicStampedReference解决ABA问题。

9其他Atomic类
9.1 AtomicIntegerArray
public class AtomicIntegerArrayTest {
    private final static AtomicIntegerArray ATOMIC_INTEGER_ARRAY = new AtomicIntegerArray(new int[]{1,2,3,4,5,6,7,8,9,10});

    public static void main(String []args) throws InterruptedException {
        Thread []threads = new Thread[100];
        for(int i = 0 ; i < 100 ; i++) {
            final int index = i % 10;
            final int threadNum = i;
            threads[i] = new Thread() {
                public void run() {
//第一个参数是数组下标,第二个参数是增加值
                    int result = ATOMIC_INTEGER_ARRAY.addAndGet(index, index + 1);
                    System.out.println("线程编号为:" + threadNum + " , 对应的原始值为:" + (index + 1) + ",增加后的结果为:" + result);
                }
            };
            threads[i].start();
        }
        for(Thread thread : threads) {
            thread.join();
        }
        System.out.println("=========================>\n执行已经完成,结果列表:");
        for(int i = 0 ; i < ATOMIC_INTEGER_ARRAY.length() ; i++) {
            System.out.println(ATOMIC_INTEGER_ARRAY.get(i));
        }
    }
}


100个线程并发,每10个线程修改数组中的一个元素。
9.2 AtomicReference
public class AtomicReferenceABATest {
    public final static AtomicReference <String>ATOMIC_REFERENCE = new AtomicReference<String>("abc");

    public static void main(String []args) {
        for(int i = 0 ; i < 100 ; i++) {
            final int num = i;
            new Thread() {
                public void run() {
                    try {
                        Thread.sleep(Math.abs((int)(Math.random() * 100)));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2")) {
                        System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
                    }
                }
            }.start();
        }
        new Thread() {
            public void run() {
                while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc"));
                System.out.println("已经改为原始值!");
            }
        }.start();
    }
}

输出结果:

我是线程:21,我获得了锁进行了对象修改!
已经改为原始值!
我是线程:52,我获得了锁进行了对象修改!

public class AtomicStampedReferenceTest {
    public final static AtomicStampedReference <String>ATOMIC_REFERENCE = new AtomicStampedReference<String>("abc" , 0);

    public static void main(String []args) {
        for(int i = 0 ; i < 100 ; i++) {
            final int num = i;
            final int stamp = ATOMIC_REFERENCE.getStamp();
            new Thread() {
                public void run() {
                    try {
                        Thread.sleep(Math.abs((int)(Math.random() * 100)));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2" , stamp , stamp + 1)) {
                        System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
                    }
                }
            }.start();
        }
        new Thread() {
            public void run() {
                int stamp = ATOMIC_REFERENCE.getStamp();
                while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc" , stamp , stamp + 1));
                System.out.println("已经改回为原始值!");
            }
        }.start();
    }
}


输出结果:
我是线程:16,我获得了锁进行了对象修改!
已经改回为原始值!

可以看到第二段代码,虽然被修改回了原值,但是只有一个线程将abc改为abc2。

10 参考资料:

http://hittyt.iteye.com/blog/1130990
http://blog.csdn.net/zz198808/article/details/8029405
http://www.infoq.com/cn/articles/atomic-operation   *
http://www.blogjava.net/xylz/archive/2010/07/01/324988.html
http://www.360doc.com/content/11/0914/16/7656248_148221200.shtml
http://ifeve.com/atomic-operation/
http://www.2cto.com/kf/201302/190174.html
http://singleant.iteye.com/blog/1405478
http://itindex.net/detail/48272-cas-%E5%8E%9F%E7%90%86-%E5%88%86%E6%9E%90
http://blog.csdn.net/jationxiaozi/article/details/6322151
http://www.th7.cn/Program/java/201302/125807.shtml
http://www.myexception.cn/program/664491.html
http://www.ibm.com/developerworks/cn/java/j-jtp11234/


--------------------------------------------------

你可能感兴趣的:(AtomicInteger原理)