Java并发编程的艺术(8)锁的内存语义

从所周知,锁可以让临界区互斥执行。

锁的释放-获取建立的happens-before关系

锁时Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。

class MonitorExample {
    int a = 0;
    public synchronized void writer() {         1 
    a++;              2
    }                3
    public synchronized void reader() {      4 
        int i = a;               5 
    ……
    }                  6
}

假设线程A执行writer()方法,随后线程B执行reader()方法。
根据happens-before规则,这个 过程包含的happens-before关系可以分为3类。
(1)根据程序次序规则,1 happens-before 2,2 happens-before 3;4 happens-before 5,5 happens- before 6
(2)根据监视器锁规则,3 happens-before 4
(3)根据happens-before的传递性,2 happens-before 5。
在这里插入图片描述
在线程A释放了锁之前所有可见的共享变量,在线程B获取同一个锁以后,将立即变得对线程B可见

锁的释放和获取的内存语义

当线程释放锁,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
当线程获取锁,JMM会把该线程对应的本地内存置为无效。从而使得被监视锁保护的临界区代码必须从主内存中读取共享变量。
在这里插入图片描述
锁释放与volatile写又相同的内存语义;锁获取与volatile读有相同的内存语义

总结

(1)线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息
(2)线程B获取一个锁,实质上是线程B接受了之前某个线程发出的(在释放这个锁之前对共享变量所做的修改的)消息
(3)线程A释放锁,随后线程B获取锁,这个过程实质上是线程A通过主内存向线程B发送消息

锁内存语义的实现

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; 
            ……
        }finally {
            lock.unlock();            释放锁
        }
    }
}

在ReentrantLock中,调用lock()方法获取锁;调用unlock()方法释放锁。
ReentrantLock的实现依赖于Java同步器框架AbstractQueuedSynchronizer(本文简称之为 AQS)。AQS使用一个整型的volatile变量(命名为state)来维护同步状态,马上我们会看到,这 个volatile变量是ReentrantLock内存语义实现的关键。
在这里插入图片描述
ReentrantLock分为公平锁和非公平锁
使用公平锁时,加锁方法lock()调用轨迹如下。

1)ReentrantLock:lock()2)FairSync:lock()3)AbstractQueuedSynchronizer:acquire(int arg)4)ReentrantLock:tryAcquire(int acquires)

在第4步真正开始加锁


 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState(); 获取锁的开始,首先会读volatile变量state
            if (c == 0) {
            	如果state=0且队列不为空,有线程正在获取锁。
                if (!hasQueuedPredecessors() &&
               		当前状态值为0,期望值也为0,则更改为更新值。
                    compareAndSetState(0, acquires)) {
                    线程获取锁成功,使用了CAS算法(单线程时偏向锁)(多线程时偏向锁变成轻量级锁)设置排他锁
                    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;
        }
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }        

加锁方法首先读volatile变量state。
在使用公平锁时,解锁方法unlock()调用轨迹如下。

1)ReentrantLock:unlock();
2)AbstractQueuedSynchronizer:release(int arg); 
3)Sync:tryRelease(int releases);

在第3步真正开始释放锁

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);      释放锁的最后,写volatile变量state
            return free;
        }

公平锁在释放锁的最后写volatile变量,在获取锁时首先读这个变量。根据volatile的happens-before规则,释放锁的线程在写volatile变量之前可见的共享变量,在获取锁的线程读取同一个volatile变量后将立即变得对获取锁的线程可见。
非公平锁释释放和公平锁完全一样,接下来看非公平锁的获取
使用非公平锁时,加锁方法lock()调用轨迹如下。

1)ReentrantLock:lock(); 
2)NonfairSync:lock();
3)AbstractQueuedSynchronizer:compareAndSetState(int expect,int update);

第3部开始真正开始加锁

protected final boolean compareAndSetState(int expect, int update) { 
	return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }

该方法以原子操作的方式更新state变量,compareAndSet()方法调用简称为CAS
JDK文档对该方法的说明如下:
如果当前状态值等于预期值,则以原子方式将同步状态 设置为给定的更新值。此操作具有volatile读和写的内存语义。

总结

公平锁和非公平锁释放时,最后都要写一个volatile变量state
公平锁获取时,首先回去读这个volatile变量
非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和写内存语义

锁释放-获取的内存语义的实现至少有下面两种方式

(1)利用volatile变量的读-写所具有的内存语义。
(2)利用CAS所附带的volatile和volatile写的内存语义。

发布了24 篇原创文章 · 获赞 1 · 访问量 544

猜你喜欢

转载自blog.csdn.net/qq_45366515/article/details/105145296