问题引出
大家可能听过「Automic」原子类,这些类在多线程下可以保证线程安全。比如想要实现自增,多线程下会出现少增加的情况。
public class VolatileAtomicTest { public static int num = 0; public static void increase() { num++; } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[10]; for (int i = 0; i { for (int j = 0; j
上面代码创建了10个线程,这10个线程要依次循环1000次,每次增加1,最后要求。
但实际情况是
多线程情况下会出现第一个线程还在运算,第二个线程就运算完并覆盖了第一个线程的值运算结果,所以会出现与预期不符的结果。原因在
public static void increase() { num++;}
num++我们可以拆解为
int a = num + 1; //步骤1num = a; //步骤2
如果线程一只执行到步骤1,还没执行到步骤2,线程二这时执行了步骤2。那么num的值就是线程2计算的值,而线程一的值就覆盖了。
如果我们保证这两步的原子性(操作一体,不能被其他线程插入)就可以得到预期结果。我们在这个方法下面加锁即可。
public synchronized static void increase() { num++;}
或者
static Lock lock = new ReentrantLock();public static void increase() { try { lock.lock(); num++; } finally { lock.unlock(); }}
但是,上面的方法都使用了锁,在多线程下对性能还是有影响的。我们可以使用无锁化的原子类,实现原子自增。
@Testpublic void test() throws InterruptedException { Thread[] threads = new Thread[10]; for (int i = 0; i { for (int j = 0; j
原子类
上图就是Java原子类的全家桶,主要是通过CAS + 自旋实现的。这里我们主要说说AtomicInteger,来看看incrementAndGet()方法。Java8增加了一些类,优化自选带来的性能问题。
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
/ setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();
发现用了Unsafe的getAndAddInt方法。至于Unsafe,底层也是操作系统的类,可以直接修改操作系统内存,或者调度线程。看着名字就知道不建议程序员使用它。
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2);//从该对象对应地址取出变量值 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;}
- var1: Object
- var2: valueOffset
- var5: 当前该变量在内存中的值
- var5 + var4: 需要写进去的值
这里就是通过CAS修改值,不断循环,知道修改成功。但在高并发情况下,存在一些问题:
❝
高并发量的情况下,由于真正更新成功的线程占少数,容易导致循环次数过多,浪费时间,并且浪费线程资源。
由于需要保证变量真正的共享,「缓存一致性」开销变大。
❞
getIntVolatile()方法是系统的本地方法
public native int getIntVolatile(Object var1, long var2);
compareAndSwapInt()也是本地方法,这里就是常说的CAS了。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
手写AtomicInteger
「首先定义几个变量」
private volatile int value;private static long offset;//偏移地址private static Unsafe unsafe;
我们定义了value值,用来保存当前的值。offset偏移地址,在类初始化的时候,计算出value变量在对象中的偏移。Unsafe类,直接操作系统内存。
「初始化变量」
// 通过Unsafe计算出value变量在对象中的偏移static { try { Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafeField.setAccessible(true); unsafe = (Unsafe) theUnsafeField.get(null); Field field = MyAtomicInteger.class.getDeclaredField("value"); offset = unsafe.objectFieldOffset(field);//获得偏移地址 } catch (Exception e) { e.printStackTrace(); }}
我们反射获取Unsafe的theUnsafe字段,自定义的value字段,还有偏移地址offset。
「自增方法」
public void increment(int num) { int tempValue; do { tempValue = unsafe.getIntVolatile(this, offset);//拿到值 } while (!unsafe.compareAndSwapInt(this, offset, tempValue, value + num));//CAS自旋}
「测试结果」
public class MyAtomTest { public static void main(String[] args) { Thread[] threads = new Thread[10]; MyAtomicInteger atomicInteger = new MyAtomicInteger(); for (int i = 0; i { for (int j = 0; j
「Atomic」原子类解决了高并发下线程安全问题,但是高并发下也带来了性能问题,如果你的项目需要使用原子类,并且性能要求高,可以使用「Java8」中的原子类。