synchronized内存语义

文章目录

  • 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

画成图就是:

总结来说就是:
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的实现示意图:

你可能感兴趣的:(并发)