java并发编程——九 AbstractQueuedSynchronizer AQS详解

AbstractQueuedSynchronizer概述

AQS用来实现锁或其他同步组件的基础框架(注意区别synchronized是在字节码上加指令方式,通过底层机器语言保证同步)。

AQS使用int类型的volatile变量维护同步状态(state),使用Node实现FIFO队列来完成线程的排队执行。在锁的实现中通过组合AQS对象的方式使用,利用AQS实现锁的语义。

  • AQS与锁(如Lock)的对比:

    锁是面向使用者的,锁定义了用户调用的接口,隐藏了实现细节;

    AQS是锁的实现者,通过用AQS简化了锁的实现屏蔽了同步状态管理,线程的排队,等待唤醒的底层操作。

    简而言之,锁是面向使用者,AQS是锁的具体实现者。

接下来我们详细看看AQS如何保证锁的临界区互斥性临界区可见性


AbstractQueuedSynchronizer的使用

AQS的设计基于模版方法,使用者继承这个abstract AQS,并重写其中的方法。AQS提供了如下final方法,与同步状态交互。

  • getState() 获取当前同步状态
    protected final int getState() {
        return state;
    }
  • setState()设置当前同步状态
    protected final void setState(int newState) {
        state = newState;
    }
  • compareAndSetState(int expect,int update) 调用unsafe底层C语言,保证原子性的改变同步状态。
    Unsafe的操作粒度不是类,而是数据和地址。
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

AQS中可选择重写的方法如下:

  • tryAcquire:独占式获取同步状态
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
  • tryRelease:独占式释放同步状态
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
  • tryAcquireShared :共享式获取同步状态,返回值>=0表示成功,否则失败。
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
  • tryReleaseShared:共享式释放同步状态
    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }
  • isHeldExclusively: AQS是否被当前线程独占
    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }

AQS提供的其他的模版方法供子类实现者调用,主要分为三类:
1独占式获取/释放同步状态的模版方法 ;
2共享式获取/释放同步状态的模版方法;
3同步队列中等待线程查看
详细分析如下:


独占式获取同步状态:

  • acquire(int arg) :通常用来实现lock(),独占式获取同步状态,如果获取失败则进入等待队列。
    需要实现AQS的tryAcquire(),如果tryAcquire()失败,则当前线程构造为结点进入同步队列等待。这个结点可能会多次阻塞—>重试tryAcquire(),直到成功
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • acquireInterruptibly:与acquire(int arg) 类似,但该方法可相应中断(抛异常)。
    public final void acquireInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
  • tryAcquireNanos :在tryAcquire()基础上增加了超时限制,如果超时则会中断等待返回false。
    public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
	if (Thread.interrupted())
	    throw new InterruptedException();
	return tryAcquire(arg) ||
	    doAcquireNanos(arg, nanosTimeout);
    }

共享式获取同步状态:

  • acquireShared(int arg):与 acquire(int arg)的类似,区别在于共享式获取同步状态,同一时刻允许多个线程获取同步状态。
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
  • acquireSharedInterruptibly:在acquireShared基础上加入了相应中断实现
    public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
  • tryAcquireSharedNanos:在acquireShared基础上增加超时限制;
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
	if (Thread.interrupted())
	    throw new InterruptedException();
	return tryAcquireShared(arg) >= 0 ||
	    doAcquireSharedNanos(arg, nanosTimeout);
    }

独占式释放同步状态的模版方法:

  • release(int arg):独占式释放同步状态
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

共享式释放同步状态的模版方法:

  • acquireShared: 共享式释放同步状态
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

同步队列中等待线程查看

  • getQueuedThreads: Returns a collection containing threads that may be waiting to acquire.返回队列中等待的线程集合。
    public final Collection<Thread> getQueuedThreads() {
        ArrayList<Thread> list = new ArrayList<Thread>();
        for (Node p = tail; p != null; p = p.prev) {
            Thread t = p.thread;
            if (t != null)
                list.add(t);
        }
        return list;
    }

AQS使用实例一:

package com.zs.juc.lock.aqs;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author zs 排它锁
 */
public class MutexLock implements Lock {

	// 通常使用静态内部类,实现自定义同步器
	private static class Sync extends AbstractQueuedSynchronizer {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		/**
		 * 当前线程是否独占这个锁
		 */
		protected boolean isHeldExclusively() {
			return getExclusiveOwnerThread() == Thread.currentThread();
		}

		/**
		 * 获取锁 0:unlocked; 1:locked
		 */
		protected boolean tryAcquire(int arg) {
			if (compareAndSetState(0, 1)) {
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
			}
			return false;
		}

		/**
		 * 释放锁
		 */
		protected boolean tryRelease(int arg) {
			if (getState() == 0) {
				throw new IllegalMonitorStateException("锁未被当前线程占用");
			}
			setExclusiveOwnerThread(null);// 置为null表示锁未被任何线程占用
			setState(0);
			return true;
		}

		/**
		 * 返回一个Condition,类似Lock实现中的Condition:await()&& signal()&&signalAll()
		 * 
		 * @return
		 */
		protected Condition newCondition() {
			return new ConditionObject();
		}
	}

	// Sync 其实就是个AQS(继承关系),这个Sync对象为使用者屏蔽了锁的实现,
	// 使用者只需要通过组合使用这个sync来实现锁的使用;
	private final Sync sync = new Sync();

	@Override
	public void lock() {
		sync.acquire(1);// AQS独占式获取锁的模版方法
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		sync.acquireInterruptibly(1);// AQS独占式可响应中断 获取锁的模版方法
	}

	@Override
	public boolean tryLock() {
		return sync.tryAcquire(1);
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		return sync.tryAcquireNanos(1, unit.toNanos(time));
	}

	@Override
	public void unlock() {
		sync.release(0);
	}

	@Override
	public Condition newCondition() {
		return sync.newCondition();
	}

	/**
	 * 当前线程是否独占锁
	 */
	public boolean isLocked() {
		return sync.isHeldExclusively();
	}

	/**
	 * FIFO队列中是否有等待获取锁的 线程
	 */
	public boolean hasQueuedThreads() {
		return sync.hasQueuedThreads();
	}

	public static void main(String[] args) {
		final MutexLock mutexLock = new MutexLock();
		// ---------------------------------Task one:
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (!Thread.interrupted()) {
					mutexLock.lock();
					try {
						System.out.println(Thread.currentThread().getName() + " acquired successfully!");
						TimeUnit.SECONDS.sleep(2);
						System.out.println(Thread.currentThread().getName() + " done!");
					} catch (InterruptedException e) {
						e.printStackTrace();
					} finally {
						mutexLock.unlock();
					}
					break;
				}
			}
		}, "Task one").start();
		// --------------------------------- Task two:
		new Thread(new Runnable() {

			@Override
			public void run() {
				while (!Thread.interrupted()) {
					mutexLock.lock();
					try {
						System.out.println(Thread.currentThread().getName() + " acquired successfully!");
						TimeUnit.SECONDS.sleep(30);
						System.out.println(Thread.currentThread().getName() + " done!");

					} catch (InterruptedException e) {
						e.printStackTrace();
					} finally {
						mutexLock.unlock();
					}
					break;
				}
			}
		}, "Task two").start();
		// --------------------------------- Task three:
		new Thread(new Runnable() {

			@Override
			public void run() {
				while (!Thread.interrupted()) {
					mutexLock.lock();
					try {
						System.out.println(Thread.currentThread().getName() + " acquired successfully!");
						TimeUnit.SECONDS.sleep(30);
						System.out.println(Thread.currentThread().getName() + " done!");

					} catch (InterruptedException e) {
						e.printStackTrace();
					} finally {
						mutexLock.unlock();
					}
					break;
				}
			}
		}, "Task three").start();
	}

}

AQS实现分析

同步队列

AQS依赖内部的同步队列(FIFO双向队列)来完成同步,当前线程获取同步状态失败时,同步器会将当前线程的引用以及等待信息构造成一个Node节点对象,并加入同步队列中,同时阻塞这个线程。当同步状态释放,会把首节点的线程唤醒,使其再次获取同步状态。


//java.util.concurrent.locks.AbstractQueuedSynchronizer.Node
    ...................
/**
 * 结点是构成同步队列(等待队列也是)的基础, 
 * 没有成功获取同步状态的结点将被加入到队列的尾部,从队列中唤醒是从头部获取结点。
 * (compareAndSetTail(...)保证加入尾部的原子性操作) 
 */
static final class Node {
    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3; 
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /**
    *注意:
    *分为两种类型的结点:共享模式结点 与 排他模式结点。
    *对于任何类型的结点,初始状态waitStatus均为0
     等待状态:
     *-SIGNAL(-1):
     *	后继结点处于等待状态,
     *	如果当前线程释放的同步状态或者被中断,
     *	将会通知后继结点,使后继结点线程运行
     * 
     *-CANCELLED(1):
     *	同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待
     *
     *-CONDITION(-2):
     *	结点在等待队列中,结点线程等待在这个Condition上,
     *	当其他线程对这个Condition对象调用signal()\signalAll(),
     *	则这个结点将进入等待队列中移入同步队列中,准备重试获取同步状态;
     *
     *-PROPAGATE(-3)
     *
     *-INITIAL(0):
     *	初始状态
     *
     *
     *;PROPAGATE;
     */
    volatile int waitStatus;

    /**
     * 前驱结点 
     */
    volatile Node prev;

    /**
     * 后继结点
     */
    volatile Node next;

    /**
     * 获取同步状态的线程
     */
    volatile Thread thread;

    /**
     * 等待队列中的后继结点。
     */
    Node nextWaiter;
    ...................

AQS同步队列结构
AQS同步队列结构

队尾增加等待结点
队尾增加结点

释放锁并唤醒后继结点,后继结点被设置为头结点,原有头结点被移除
对头结点唤醒并设置为头结点

独占锁的获取与释放

这里写图片描述

public final void acquire(int arg) {
        /*
         * Step:
         * 
         * 1.尝试获取锁.tryAcquire(arg)
         * 
         * 2. addWaiter(Node.EXCLUSIVE)
         * 尝试获取锁失败,则把当前线程构造为Node对象并且排他模式放入sync同步队列中.
         * 同步队列的好处是:a.同步等待线程,实现公平锁(FIFO)。b.线程通信减小到最低,每个线程的等待唤醒由各自前驱结点完成。
         * 
         * 3 acquireQueued
         * 
         */
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();// 线程中断标志置为true,只是顺带一个标志位的维护
    }

详细说来:

1.
tryAcquire尝试获取同步状态,这个方法必须被子类实现。如果成功,直接返回。否则:

//需要由子类实现:原子性、排他的访问state
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

2.
将当期线程构造为一个排他模式(等待排他锁)的结点,并且插入同步队列的队尾,如果插入失败(compareAndSetTail(…)失败),自旋直到成功。

    private Node addWaiter(Node mode) {
        // 此处增加了mode这个参数
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // 快速增加结点;如果增加操作失败,使用enq自旋方法
        Node pred = tail;
        if (pred != null) {// 如果null==pred,表示同步队列未初始化
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {// 在尾部增加node结点
                pred.next = node;// node结点连接之前的尾结点
                return node;// 返回新的尾结点
            }
        }
        enq(node);// 自旋, sync队列未初始化或者在队列尾部添加结点失败时执行,把node结点放入尾部.
        return node;
    }

   private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

3.
进入forloop,如果前驱结点已经为头结点了(说明排队快到自己了,就等前驱结点释放了),那么再次尝试tryAcquire(…):
如果获取锁成功,将当前结点设置为头结点,并移除原头结点,成功返回。
如果获取锁失败,判断是否可以阻塞(后文述),可以阻塞那么调用park方法阻塞,否则重新forloop头部开始一遍上述过程。

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {// 自旋以尝试获取锁,直到发现node的前驱是头结点并且node获取状态成功,则释放头结点
                final Node p = node.predecessor();// 获取当前结点(sync的尾结点)的前驱结点
                if (p == head && tryAcquire(arg)) {// 如果这个前驱结点是头结点,则再次尝试获取锁.只有头结点可以获取锁
                    setHead(node);// 获取锁成功,则当前结点设为头结点。头结点所对应的含义是当前占有锁且正在运行。
                    p.next = null; // help GC。与上一步操作共同完成上一个头结点的释放
                    failed = false;
                    return interrupted;// 自旋结束,原有头结点被删除,当前结点为头结点并且获取到锁
                }
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())// parkAndCheckInterrupt:
                                                                                     // 进入waiting状态,停止线程调度器对当前节点线程的调度。
                    interrupted = true;// 开始进入了阻塞
            }
        } finally {
            if (failed)
                cancelAcquire(node);// 把node从sync队列删除
        }
    }

4.
shouldParkAfterFailedAcquire:
true 允许立刻阻塞:
当前结点的前驱结点为signal状态,那么可以阻塞(因为前驱结点signal状态会及时唤,醒自己)

false 不允许立刻阻塞,会重新执行这个forloop。(根据当前结点的前驱结点状态,分两种情况):

ws>0(只有CANCELLED状态),说明前驱结点无效需要跳过,使用do-while 从同步队列中移除这个cancelled的前驱结点,依次向前遍历,直到找到一个非canncelled的结点后,重新设为自己的前驱结点。

ws<=0只能是 0(默认初始态)或者是PROPAGATE态。在这种情况下要阻塞当前结点,需要先把当前结点的状态设置为SIGNAL(那么下一次进入这个方法肯定会返回true)。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

5.
如果 shouldParkAfterFailedAcquire返回了true,那么会执行parkAndCheckInterrupt()。调用park(this)立刻阻塞当前线程,直到自己的前驱结点释放锁把自己唤醒(release方法中)。

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//CAS获取锁-修改state
            Node h = head;
            //头结点不为空(说明队列初始化了)&&头结点状态不为0(说明有后继结点需要唤醒)
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//唤醒后继结点
            return true;
        }
        return false;
    }

总结:
1.tryAcquire尝试获取状态(锁),需要保证原子性、互斥性,因为多个线程可能同时获取,使用JNI的CAS。

2.如果获取失败,addWaiter()构造Node结点并放入同步队列尾部:
第一次直接把新构造的结点放入尾部(compareAndSetTail),如果失败进入enq方法,一直自旋放入队尾,直到成功为止.

3.结点放入尾部后,acquireQueued中判断当前结点的前驱结点是否为头结点&&当前结点CAS获取锁成功,两个条件满足则删除头结点,当前结点获取锁并置为头结点。

如果失败,对当前前节点的前驱节点的状态进行判断:

如果是signal状态(确保park后能够被前驱结点唤醒)是则尝试park操作;

如果是cancelled状态则跳过这个结点,向前找直到找到一个不是cancelled状态的结点,设置为当前节点的前驱节点,然后再次获取锁;
如果是 0或者是PROPAGATE状态,则CAS替换这个前驱结点状态为signal,然后再次尝试获取锁。

可以看出这个forloop最多可以执行三次(前驱结点是Cancelled状态,并且替换后的前驱结点状态是propagate或者0)

4.如果自旋获取锁失败,并且满足park条件, LockSupport.park(this)进入waiting状态,直到它的前驱结点被取消或者释放锁时才被唤醒(对应release中的unpark方法)


独占式超时获取

两个synchronized不具备的特点:
1.可设置超时
2.响应中断。

与独占式锁非常类似,下边贴出核心代码:

   /**
     * Acquires in exclusive timed mode.
     *
     * @param arg the acquire argument
     * @param nanosTimeout max wait time
     * @return {@code true} if acquired
     */
    private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;// 超时时刻对应的时间戳
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                // 在自旋获取锁中,检查超时,一旦超时立刻返回false
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)//距离超时时间大于1000纳秒时,park效率更高;否则不去park,继续自旋
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())//响应中断
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

共享式锁的获取与释放

共享式锁的获取:
代码与上述独占式获取锁比较类型,以下详细分析不同之处。

//与独占式类似
	public final void acquireShared(int arg) {
		if (tryAcquireShared(arg) < 0)
			doAcquireShared(arg);
	}

共享式获取锁的逻辑与独占式获取逻辑的区别主要体现在如下setHeadAndPropagate(node, r)方法中:

private void doAcquireShared(int arg) {
		// 1.将当前线程构建为一个共享模式结点,并尝试插入队列尾部,直到成功返回
		final Node node = addWaiter(Node.SHARED);
		boolean failed = true;
		try {
			boolean interrupted = false;
			for (;;) {
				final Node p = node.predecessor();
				if (p == head) {// 2.当这个结点的前驱结点为头结点(头结点肯定是获取锁的结点)。
					int r = tryAcquireShared(arg);// 再次尝试获取锁
					if (r >= 0) {// 表示获取锁成功

						//关键点:这个setHeadAndPropagate(..)执行后,所有的共享式结点均被依次唤醒依次更新为头结点(唤醒所有共享模式结点,为了方便tryAcquireShared中的多锁获取。后继共享结点会在acquireShared.tryAcquireShared成功获取锁
						
						//实现者(如writereadLock)一般会在tryAcquireShared()中实现多个线程获取锁)
						
						setHeadAndPropagate(node, r);
						p.next = null; // help GC
						if (interrupted)
							selfInterrupt();
						failed = false;
						return;
					}
				}
				if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
					interrupted = true;
			}
		} finally {
			if (failed)
				cancelAcquire(node);
		}
	}
private void setHeadAndPropagate(Node node, int propagate) {
		Node h = head; // Record old head for check below
		setHead(node);
		
		//任一为true即可:
		// propagate>0表示后继结点可能共享式获取锁
		// 原有头结点不存在,可能已被gc
		// 原有头结点的状态不为cancelled
		// 当前结点(也就是当前头结点)不存在
		// 当前结点(也就是当前头结点)的状态不为cancelled

		if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
			Node s = node.next;
			if (s == null || s.isShared())//要么当前结点没有后继者,要么当前结点后继者为共享模式结点
				doReleaseShared();
		}
	}
private void doReleaseShared() {
	for (;;) {
		Node h = head;
			if (h != null && h != tail) {
				int ws = h.waitStatus;
				//参考shouldParkAfterFailedAcquire,Node.SIGNAL代表当前结点的后继结点阻塞
				if (ws == Node.SIGNAL) {
					//head结点置为0状态
					if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
						continue;
					//唤醒后继者
					unparkSuccessor(h);
					} 
			//如果头结点位初始默认状态,那么尝试CAS修改为propagate状态。
			//失敗重新执行一遍forloop代码。注意Node.PROPAGATE,只在这个地方被使用
				
			//如果状态为0代表没有后继结点阻塞的情况,也就是没有后继结点
			else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
					continue; 
			}
			if (h == head) // loop if head changed
				break;
		}
	}

共享式释放锁:
上文已经分析,不赘述

	public final boolean releaseShared(int arg) {
		if (tryReleaseShared(arg)) {
			doReleaseShared();
			return true;
		}
		return false;
	}

总结:可以看到共享锁的获取中,如果原头结点非共享模式,而当前结点是共享模式,会进入doAcquireShared方法。

非常类似独占式获取锁的部分,不赘述。只分析不同之处:

如果前驱结点是头结点,那么再次尝试获取锁,如果获取锁(当然是共享锁)成功,那么会有一个特殊的动作:setHeadAndPropagate(node, r)

关键点:
在这个setHeadAndPropagate(node, r)中:

将会唤醒该结点的所有共享模式的后继结点,那么可以预料到共享模式的后继结点会尝试获取锁成功(tryAcquireShared),依次将自己置为头结点,然后唤醒自己的后继者,新的后继者又会获取锁成功,将自己置为头结点,唤醒自己的后继者…直到所有的共享结点全部唤醒,并且获取了锁。如果最后边的共享结点的后继者为一个排他模式的结点,那么也会被唤醒,再次尝试排他获取。

AQS(或JDK锁)如何保证可见性?

JDK锁是通过AQS去实现的,锁的语义必须要保证临界区数据的可见性。之前我们已经说了volatile、synchronized如何保证可见性。

那么JDK锁的可见性怎么保证的呢?它也是来自AQS,以ReentrantLock为例具体来看:

lock:

    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
			
			
 
unlock:

        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;

那么接着看看tryAcquire与tryRelease怎么做的?提前说下这里是重点:

tryAcquire
            
			final Thread current = Thread.currentThread();
            int c = getState();//state读操作
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {//state 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);//state 写操作
                return true;
            }
            return false;
        		
		
		
tryRelease		
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);//state 写操作
            return free;

我们简化下来,去掉跟这个论点关系不大的代码:

lock:
   state 读操作; // step1   
   state 写操作;// step2   
   ....
---------锁已经被获取,开始执行临界区代码-------------
	{
	临界区 业务代码。。。// step3  
	}
 ---------临界区代码执行完,准备释放锁-------------
 unlock:   
   state 写操作// step4  
   ...  
 ----------------释放锁---------------------
 

根据两条happens-before JMM中抽象出的happens-before规则 :

A happens before B,B happens before C——>A happens before C

锁的释放 happen-before 锁的获取

对一个volatile 变量的写 happens before 这个变量的读(也就是说写线程写了后立马可以被读线程看到,立马可见)

现在我们举个具体例子,比如线程A释放锁后,线程B对线程A执行的临界区代码是否可见?

因为上述三条规则保证了如下可见性顺序(在前的一定对在后的可见)

(ThreadA 释放了锁后,假设ThreadB即将获取锁)

ThreadA.step1\ThreadA.step2——>ThreadA.step3——>ThreadA.step4
——>ThreadB.step1\ThreadB.step2——>ThreadB.step3——>ThreadB.step4.

有此,我们可以看到ThreadA.step3对ThreadB.step3可见!!

java并发编程——java内存模型/happens-before

关于AQS的其他方法,在它具体的实现组件(如Condition、ReadWriteLock等)中讲解。可参考我的其他文章。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值