java并发包 Atomic
CAS机制
除了synchronized之外,java还提供了一些并发包。比如现在这段代码,肯定会有并发问题,我们当然可以通过重磅的 synchronized 锁来解决多线程并发问题,但是这样就有点杀鸡用牛刀了。我们可以用Atomic原子类来解决这个问题。
import java.util.concurrent.atomic.AtomicInteger; public class Demo { // 初始值0 private static int num = 0; public static void inCreate(){ //自增 num++; } // Atomic 解决并发问题 // // 初始值0 // private static AtomicInteger num = new AtomicInteger(0); // public synchronized static void inCreate(){ // // 自增 // num.incrementAndGet(); // } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { inCreate(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } Thread.sleep(2000);// 等待两秒,让上面10个线程先执行完 System.out.println(num);// 结果为1000才是对的 } }
Atomic 原子类不是传统意义的锁机制,它是无锁化的 CAS 机制,通过 CAS 机制保证多线程修改个数值的安全性,CAS的全称是:Compare and Set,也就是先较再设置的意思。
Atomic源码
我们在操作 atomic 的时候,实际上就是对 value 的修改,它是被 volatile 修饰的,也就是说变更之后马上就可以被其他线程感知到。
我们调用 incrementAndGet 自增方法,就是通过 Unsafe 类来做的,它负责执行 CAS操作。它是jdk内部的一个类,我们在自己的系统中使用 Unsafe.getUnsafe() 是会报错的,因为类加载器是不同的。
先分析下参数,看看它整个的调用过程:
var1:就是我们传入的this,就是 AtomicInteger 这个类对象。
var2:就是 valueOffest,也就是我们的value的偏移量。
var4:增加的值,1。
var5:内存中旧的值。
首先通过 this.getIntVolatile(var1, var2); 根据 AtomicInteger 的value偏移量,获取到了内存中旧的值 var5 ,就是我们当时设置的0。
然后通过 while 无限循环去执行 this.compareAndSwapInt(AtomicInteger, valueOffest, 0, 0 + 1)。这个过程就是 CAS 操作,它是被 native 修饰的,底层就是 C 发送的 cpu 指令,通过一个线程对某块小内存中的数据进行修改。
最后就把旧的值 var5 返回来了,然后 return 0+1,我们就看到了新的值 1。
CAS优化
刚才我们模拟CAS执行过程知道,要是获取到的值和内存不一致,那么它会一直无线循环;如果是大量线程同时涌入,必然会导致大量线程空循环,性能和效率都不是特别好。所以java8使用LongAdder使用分段的方式进行了优化。在longadder中,一开始所有线程都是对base进行操作。如果线程较多,就会实行分段CAS机制,也就是内部会搞一个cell数组,每个数组是一个数值分段,让每一个cell去处理一些请求;如果某一个cell执行cas失败了,则会自动去找另一个cell进行cas操作;最后将所有cell分段数值加起来返回给你。
// LongAdder 解决并发问题 // 初始值0 private static LongAdder num = new LongAdder(); public synchronized static void inCreate(){ // 自增 num.add(1); }