1.Atomic原子类
Atomic原子类就是具有原子/原子操作的类,具体类别见下图
按照类型分,可以分为基本数据类型,数组类型,引用类型,升级类型,累加器。
AtomicInteger用的比较多,介绍一下其方法。
获取值
/** * Gets the current value. * * @return the current value */ public final int get() { return value; }
获取值然后设置为新值
/** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); }
获取值,自增/自减,注意是先获取值,再自增/还有相同的incrementAndGet和decrementAndGet(自增,自减后再获取)
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } /** * Atomically decrements by one the current value. * * @return the previous value */ public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); }
获取后相加
/** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the previous value */ public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); }
比较后,如果相等则替换为新值
public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
2.Atomic原子类的作用以及原理
在多线程同时操作一个变量,i++的时候,通常是用synchronized来解决。有了原子类后,我们可以通过用原子类来定义变量,在线程安全的前提下,原子操作类更加简单,高效。
测试类
public class AtomicTest { public static void main(String[] args) { Addself addself = new Addself(); for(int i =0; i<10; i++) { new Thread() { @Override public void run() { try { System.out.println(addself.getCount()); } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); } } }
实现类
public class Addself { //1使用原子类的自增,获取方法 private AtomicInteger count = new AtomicInteger(0); public int getCount() throws InterruptedException { Thread.sleep(1000); return count.incrementAndGet(); } //2不使用任何多线程关键字 // private int count = 0; // // public int getCount() throws InterruptedException { // count++; // Thread.sleep(1000); // return count; // } //3加volatile和synchronized修饰变量与方法 // private volatile int count = 0; // // public synchronized int getCount() throws InterruptedException { // count++; // Thread.sleep(1000); // return count; // } }
其中case1,虽然每个线程都等待了1s,但是会发现,10个线程不到2s就跑完了
6 10 4 7 2 9 8 1 5 3
case2,木有考虑多线程,由于等待了1s,导致获取到的全是10.
10 10 10 10 10 10 10 10 10 10
case3,利用synchronized锁住了实例方法,每次只能一个线程去运行,所以在控制台上1~10是每隔1s打印出来的(如果不考虑其他运行的时间)
1 2 3 4 5 6 7 8 9 10
那Atomic原子类是怎么保证线程安全的。
比如AtomicInteger,其中的value定义是这样的,用了volatile来修饰该变量。保证了该变量的可见性,在JVM中取到的是最新的值。
// setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value;
下面的就有意思了,看源码果然不会让人失望,你会发现,除了value还有一个叫valueOffest的变量,修饰符是private static final long 。然后你再往下看会发现有很多方法都用到了这个参数,然后还会发现,基本上很多方法都用到了unsafe.function()
而里面的参数都是(this,valueOffset,?),这个参数是通过unsafe.objectFieldOffset获取到的原来值的内存地址。
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
然后执行获取自增方法时,会先比较当前值与原来值是否相等,如果相等,则进行后面的操作。
所以说,Atomic原子类是通过 CAS (compare and swap) + volatile 和 native 方法来保证原子操作的。
3.ABA问题
你会发现,上面说到,只会在用的时候比较2个值是否一样,但是没有在乎过程,就会出现ABA问题,大概意思就是,线程1拿到了值为20,但是在等待IO,然后线程2拿过去,先改成了30,然后有改成了20,线程2结束,线程1刚好继续运行,线程1一比较,欸,还是20啊,那我继续用了。看起来没问题,但是中间有个值变化的过程。咋办呢,别慌~,有个AtomicStampedReference类,在类中维护了一个stamp变量,可以理解为版本号,在cas过程中,除了比较原来的,还需要比较版本号,都一样才继续执行下面的操作。还有一个类似的类AtomicMarkablereference,同样用来一个变量,不过该变量是Boolean的,只能说能够减少ABA情况的发生,但不能杜绝.
革命尚未成功,同志仍需努力,加油!