java concurrent 包的基石 volatile 和 锁的原理分析

java concurrent 包的基石 volatile 和 锁的原理分析

在concurrent包中,用到很多的锁,如 ReentrantLock锁,里面的核心原理是通过
volatile 的和cas进行操作的。

volatile 的读语义和写语义的核心就是禁止指令的重排序。
由于在编译器和处理器会对指令进行重排序,进行指令的优化,但有时这种重排序会导致计算结果的不一致性。
所以由此涉及到数据的依赖性和hapen-befor的概念。
在并发模型中,通过对volatile的读语义和写语义进行控制,保证了数据的一致性。

下面对volatile写和volatile读的内存语义做个总结。

  • 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
  • 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。
  • 线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来
禁止特定类型的处理器重排序。下面是基于保守策略的JMM内存屏障插入策略

  • 在每个volatile写操作的前面插入一个StoreStore屏障。
  • 在每个volatile写操作的后面插入一个StoreLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadStore屏障。

通过上面的volatile的语义,保证了指令执行的正确性,不怕指令的重排序引起的业务错误问题。

cas 其实也具有了volatile的读和写的语义。
cas 通过如下方法进行处理
unsafe.compareAndSwapInt(this, stateOffset, expect, update)
这里调用到了c++的源码,在c++源码当中,会插入一个lock前缀的内存屏障

intel的手册对lock前缀的说明如下。

  • 1)确保对内存的读-改-写操作原子执行。在Pentium及Pentium之前的处理器中,带有lock前
    缀的指令在执行期间会锁住总线,使得其他处理器暂时无法通过总线访问内存。很显然,这会
    带来昂贵的开销。从Pentium 4、Intel Xeon及P6处理器开始,Intel使用缓存锁定(Cache Locking)
    来保证指令执行的原子性。缓存锁定将大大降低lock前缀指令的执行开销。
  • 2)禁止该指令,与之前和之后的读和写指令重排序。
  • 3)把写缓冲区中的所有数据刷新到内存中。
    上面的第2点和第3点所具有的内存屏障效果,足以同时实现volatile读和volatile写的内存语义。

下面我们分析下 ReentrantLock 的源码,可以看到,其实低层的核心就是一个 volatile 的变量和 cas的操作

//通过volatile的读写语义,保证数据的一致性

private volatile int state

 protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }


     protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    //调用到了unsale的低层c++源码,具有了volatile的读写语义
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

通过上面的数据的设置,在低层保证了锁的获取的一致性。

也可以分析一下AtomicInteger 类,其低层就是对一个
volatile int value 变量的修改
然后对其的原子修改也是调用到了
unsafe.compareAndSwapInt(this, valueOffset, expect, update)

通过分析 AtomicBoolean 类也一样,里面只是创建了
volatile int value变量,通过 0 1 表示true或者false

总结:

java coucurrent包里面的相关原子类,或者锁,其实都是基于
volatile 和cas的相关语义进行的,就是这整个目录的核心。在其之上的ConcurrentHashMap和
Queue也是基于这一低层进行设计

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值