天天看点

synchronized内存语义1、锁释放、获取与happens-before关系2、锁释放、获取的内存语义3、锁内存语义的实现4、concurrent包的实现

文章目录

  • 1、锁释放、获取与happens-before关系
  • 2、锁释放、获取的内存语义
  • 3、锁内存语义的实现
  • 4、concurrent包的实现

锁(synchronized)除了让临界区互斥执行,其内存语义也要重视。

1、锁释放、获取与happens-before关系

锁除了互斥执行代码,还能让释放锁的线程向另一个拿锁的线程发消息。

public class SynchronizedExample {

    int a = 0;

    public synchronized void writer() { //1
        a++;                            //2
    }                                   //3

    public synchronized void reader() {  //4
        int b = a;                      //5
        System.out.println("b -> " + b);    //6
        //...
    }
    
}

           

假如线程A执行完writer(),B线程开始执行reader()。根据happens-before原则(hb原则)可分为两类:

  1. 程序次序规则
  • 1 > 2 > 3
  • 4 > 5 > 6
  1. 监视器锁规则
  • 3 > 4

综合1)、2) ,可知 2 > 5

画成图就是:

synchronized内存语义1、锁释放、获取与happens-before关系2、锁释放、获取的内存语义3、锁内存语义的实现4、concurrent包的实现

总结来说就是:

A在释放锁之前所有可见的共享变量,在B拿锁之后就对B可见!

2、锁释放、获取的内存语义

锁释放时,会将持锁线程的工作内存的共享变量值刷到主内存;

拿到锁时,JMM将持锁线程的工作内存置为无效,临界区代码必须从主内存中读共享变量。

锁释放与volatile写;锁获取与volatile读有相同内存语义!

小结下:

  • A释放锁 ,即 A向后来将拿锁的线程B发送一个消息
  • B拿锁,即B接收了之前释放锁的线程A的一个消息
  • A释放锁、B拿锁,其实就是A向B发送个消息。

3、锁内存语义的实现

本文借助

ReentrantLock

的一个demo说明

public class ReentrantLockExample {

    int a = 0;
    ReentrantLock lock = new ReentrantLock();

    public void writer() {
        lock.lock();
        try {
            a++;
        } finally {
            lock.unlock();
        }
    }

    public void reader() {
        lock.lock();
        try {
            int i = a;
            System.out.println(" i --> " + i);
        } finally {
            lock.unlock();
        }
    }
}
           

这里首先补充下:CAS是同时具有

volatile

的读写内存语义的。

原因简单来说就是CAS指令执行前会有

lock

前缀指令。

lock

前缀指令能

  • 1)确保对内存的读-改-写操作原子执行
  • 2)禁止该指令和之前、之后的读和写指令重排
  • 3)将写缓冲区的数据刷到内存

2)3)具有的内存屏障效果,足以同时实现

volatile

读写的内存语义。可见:CAS是有

volatile

读写内存语义的。

ReentrantLock

的内部实现依赖于

AQS

。AQS使用一个整型的

volatile

维护同步状态。

ReentrantLock

分公平锁与非公平锁。

  • 对于公平锁: 加锁方法首先读

    volatile

    变量

    state

    ;在释放锁时,最后写

    volatile

    变量

    state

  • 对于非公平锁:加锁首先会用CAS更新

    volatile

    变量

    state

    ,该操作同时具有

    volatile

    的读写语义。

    释放锁时,同样要写

    volatile

    变量

    state

此处省略具体代码分析

其实从中也可以看出:要实现

synchronized

释放、获取的内存语义,至少有两种方式:

  • 利用

    volatile

    读写的内存语义
  • 利用CAS附带的

    volatile

    读与

    volatile

    写的内存语义。

4、concurrent包的实现

CAS同时有

volatile

读写的内存语义,因此Java线程有4种通信方式。

  • A写

    volatile

    ,B读
  • A写

    volatile

    ,B用CAS更新
  • A用CAS更新,B随后也用CAS更新
  • A用CAS更新,B读这个

    volatile

1)CAS是机器级别的指令,能以原子方式对内存执行读-该-写操作;

2)同时,

volatile

的读写和CAS可以实现线程间的通信。

将1) 2) 整合在一起,能实现强大的功能的,构成JUC实现的基石。

看JUC源码后,能抽象出一个通用模式:

  • 声明一个volatile
  • 用CAS更新线程间同步
  • 配合

    volatile

    读写和CAS所具有的

    volatile

    读写内存语义来实现线程间通信。

从实践上来说,JUC的 AQS、非阻塞数据结构、原子类等基础类就是使用这种模式实现的。下图是JUC的实现示意图:

synchronized内存语义1、锁释放、获取与happens-before关系2、锁释放、获取的内存语义3、锁内存语义的实现4、concurrent包的实现