案例一
public class AqsThread extends Thread {
private Lock lock;
public AqsThread(String name, Lock lock) {
super(name);
this.lock = lock;
}
@Override
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "running");
} finally {
lock.unlock();
}
}
}
public class AqsDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
AqsThread t1 = new AqsThread("t1", lock);
AqsThread t2 = new AqsThread("t2", lock);
t1.start();
t2.start();
}
}
执行案例的运行结果.(其中之一)
线程t1先执行lock操作,获取锁.然后线程t2执行lock操作.然后t1进行unlock操作,然后t2获取锁成功,接着执行unlock操作.
t1线程调用lock.lock方法,方法调用顺序.
t2线程调用lock.lock方法,其方法调用顺序.
最后的结果是被阻塞.源码如下.
t1线程调用lock.unlock,其方法调用顺序.
t1线程中调用lock.unlock后,经过一系列的调用,最终的状态是释放了许可,因为调用了LockSupport.unpark。这时,t2线程就可以继续运行了。此时,会继续恢复t2线程运行环境,继续执行LockSupport.park后面的语句.
进一步调用.
t2线程调用lock.unlock,其方法调用顺序
t2线程执行lock.unlock后,最终达到的状态还是与之前的状态一样.
案例二
public class DepotDemo {
private int size;
private int capacity;
private Lock lock;
private Condition fullCondition;
private Condition emptyCondition;
public DepotDemo(int capacity) {
this.capacity = capacity;
lock = new ReentrantLock();
fullCondition = lock.newCondition();
emptyCondition = lock.newCondition();
}
public void produce(int no) {
int left = no;
lock.lock();
try {
while (left > 0) {
while (size >= capacity) {
System.out.println(Thread.currentThread().getName() + " before await");
fullCondition.await();
System.out.println(Thread.currentThread().getName() + " after await");
}
int inc = (left + size) > capacity ? (capacity - size) : left;
left -= inc;
size += inc;
System.out.println("produce = " + inc + ", size = " + size);
emptyCondition.signal();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consume(int no) {
int left = no;
lock.lock();
try {
while (left > 0) {
while (size <= 0) {
System.out.println(Thread.currentThread() + " before await");
emptyCondition.await();
System.out.println(Thread.currentThread() + " after await");
}
int dec = (size - left) > 0 ? left : size;
left -= dec;
size -= dec;
System.out.println("consume = " + dec + ", size = " + size);
fullCondition.signal();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ReentrantLockTest {
static class Consumer {
private DepotDemo depot;
public Consumer(DepotDemo depot) {
this.depot = depot;
}
public void consume(int no) {
new Thread(new Runnable() {
@Override
public void run() {
depot.consume(no);
}
}, no + " consume thread").start();
}
}
static class Producer {
private DepotDemo depot;
public Producer(DepotDemo depot) {
this.depot = depot;
}
public void produce(int no) {
new Thread(new Runnable() {
@Override
public void run() {
depot.produce(no);
}
}, no + " produce thread").start();
}
}
}
class ReentrantLockDemo {
public static void main(String[] args) throws InterruptedException {
DepotDemo depot = new DepotDemo(500);
new ReentrantLockTest.Producer(depot).produce(500);
new ReentrantLockTest.Producer(depot).produce(200);
new ReentrantLockTest.Consumer(depot).consume(500);
new ReentrantLockTest.Consumer(depot).consume(200);
}
}
通过静态分析.可以得出如下调用流程.
p1线程调用lock.lock,获得锁,继续运行. p2线程调用lock.lock,条件不满足,被放入条件队列阻塞等待唤醒.
c1线程调用lock.lock.
c2线程调用lock.lock.
通过上面的分析.阻塞的时候会释放锁进入条件队里里等待. 被唤醒后会重新进入抢锁队里.
p1线程执行emptyCondition.signal,其方法调用顺序.
p1线程执行lock.unlock,示意图如下.
p2成为头节点.获取到锁继续执行.c1和c2还处于阻塞状态.
p2线程执行fullCondition.await,其方法调用顺序.
新生成一个节点放入到条件队列,并且释放锁.
继续运行c1线程,c1线程由于之前被park了,所以此时恢复,继续之前的步骤,即还是执行前面提到的acquireQueued方法,之后,c1判断自己的前驱结点为head,并且可以获取锁资源.
c1线程执行fullCondtion.signal,方法调用顺序 .
signal方法达到的最终结果是将包含p2线程的结点从condition queue中转移到sync queue中,之后condition queue为null,之前的尾结点的状态变为SIGNAL。
c1线程执行lock.unlock操作,根据之前的分析,经历的状态变化.
c2线程执行emptyCondition.await
await操作将会生成一个结点放入condition queue中与之前的一个condition queue是不相同的,并且unpark头节点后面的结点,即包含线程p2的结点。
p2线程被unpark,故可以继续运行,经过CPU调度后,p2继续运行,之后p2线程在AQS:await方法中被park,继续AQS.CO:await方法的运行.
p2继续运行,执行emptyCondition.signal.
最终,将condition queue中的结点转移到sync queue中,并添加至尾部,condition queue会为空,并且将head的状态设置为SIGNAL。
p2线程执行lock.unlock操作,根据前面的分析可知.
unlock操作会释放c2线程的许可,并且将头节点设置为c2线程所在的结点。
整个流程就结束了.只要仔细研究AQS源码.这些运用基本上会很好理解.多读多看.每一次重复的看,都会有新的理解.