并发编程学习目录
并发编程(1)-java中的6中线程状态
并发编程(2)-怎么中断线程?
并发编程(3)-synchronized的实现原理
并发编程(4)-深入理解volatile关键字
并发编程(5)-ReentrantLock源码分析
一、用ReentrantLock加condition完成生产消费者模型
在前面学习 synchronized 的时候,有讲到 wait/notify 的基本使用,结合 synchronized 可以实现对线程的通信。那么这个时候我就在思考了,既然 J.U.C 里面提供了锁的实现机制,那 J.U.C 里面有没有提供类似的线程通信的工具呢? 于是找阿找,发现了一个Condition 工具类。Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒。
condition基本使用:
生产者:
package com.gupao.condition;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class Product implements Runnable {
private Queue<Integer> queue;
private Lock lock;
private Condition condition;
private int maxSize;
public Product(Queue<Integer> queue, Lock lock, Condition condition, int maxSize) {
this.queue = queue;
this.lock = lock;
this.condition = condition;
this.maxSize = maxSize;
}
@Override
public void run() {
int i = 0;
while (true) {
i++;
lock.lock();
while (queue.size() == maxSize) {
try {
System.out.println("队列满了!");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
queue.add(i);
System.out.println("生产者生产消息" + i);
condition.signal();
lock.unlock();
}
}
}
消费者:
package com.gupao.condition;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class Customer implements Runnable {
// 对列
private Queue<Integer> queue;
// 锁
private Lock lock;
private Condition condition;
// 队列的大小
private int maxSize;
public Customer(Queue<Integer> queue, Lock lock, Condition condition, int maxSize) {
this.queue = queue;
this.lock = lock;
this.condition = condition;
this.maxSize = maxSize;
}
@Override
public void run() {
int i = 0;
while (true) {
i++;
lock.lock();
while (queue.size() == 0) {
try {
System.out.println("队列空了!");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Integer remove = queue.remove();
System.out.println("消费者消费消息" + remove);
condition.signal();
lock.unlock();
}
}
}
测试类:
public class main {
public static void main(String[] args) throws InterruptedException {
Queue<Integer> queue = new LinkedBlockingQueue<>();
int maxSize = 5;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread t1 = new Thread(new Product(queue,lock,condition,maxSize));
Thread t2 = new Thread(new Customer(queue, lock, condition, maxSize));
t2.start();
Thread.sleep(500);
t1.start();
}
}
二、源码分析
该方法是建立在已经拿到锁的情况下。想了解lock的原理的可以看我另外一篇博客
ReentrantLock原理分析
调用Condition,需要获得 Lock 锁,所以意味着会存在一个 AQS 同步队列,在上面那个案例中,假如两个线程同时运行的话,那么 AQS 的队列可能是下面这种情况
调用Condition 的await()方法(或者以 await 开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。
public final void await() throws InterruptedException {
// 该方法允许被中断
if (Thread.interrupted())
throw new InterruptedException();
// 创建一个新的节点加入condition队列中,节点状态为condition
Node node = addConditionWaiter();
// 释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
while (!isOnSyncQueue(node)) {
// 判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已经释放锁了
// 通过 park 挂起当前线程
// 上下文切换(计数器、寄存器) 挂起保存,唤醒的时候恢复
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 当这个线程醒来,会尝试拿锁, 当acquireQueued返回 false 就是拿到锁了.
// interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让其入队了.
// 将这个变量设置成 REINTERRUPT.
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点.
// 如果是 null ,就没有什么好清理的了.
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 如果线程被中断了,需要抛出异常.或者什么都不做
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter
这个方法的主要作用是把当前线程封装成 Node,添加到等待队列。这里的队列不再是双向链表,而是单向链表
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
// 如果最后一个节点不等于condtion状态,则从链表中删除,不是condition状态就是cancelled状态
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 创建一个新的节点,状态是condition,加入到链表中,这里是单项链表
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
unlinkCancelledWaiters
这个方法是清理掉链表中的状态为cancelled状态的节点
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
// 如果首节点不为空
while (t != null) {
// 获取到下个节点
Node next = t.nextWaiter;
// 如果该节点的状态不等于conditon,则该节点需要在链表中删除
if (t.waitStatus != Node.CONDITION) {
// 该节点的下个节点设置为空,意味着垃圾回收后就回收该节点
t.nextWaiter = null;
// trail 为空,则把下一个节点负责给首节点
if (trail == null)
firstWaiter = next;
else
// 把下一个节点赋值给next,这样链表就要继续连接起来
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
// 等于condtion,把该节点赋值给尾节点
else
trail = t;
// 下个一个节点赋值给t,进行下一次循环
t = next;
}
}
图解分析
执行完addConditionWaiter 这个方法之后,就会产生一个这样的condition 队列
fullyRelease
fullRelease,就是彻底的释放锁,什么叫彻底呢,就是如果当前锁存在多次重入,那么在这个方法中只需要释放一次就会把所有的重入次数归零。
final int fullyRelease(Node node) {
boolean failed = true;
try {
// 获得重入锁的状态
int savedState = getState();
// 释放锁,并且唤醒同步队列中的下一个线程
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
图解分析
此时,同步队列会触发锁的释放和重新竞争。ThreadB 获得了锁。
isOnSyncQueue
判断当前节点是否在同步队列中,返回 false 表示不在,返回true 表示如果不在,
讲当前线程挂起
final boolean isOnSyncQueue(Node node) {
// 如果状态是condition,证明一定不再同步队列里,condition状态只存在于等待队列,在同步队列里,node.prev是一定不为空的,因为有个head的节点
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 在等待队列里,node.next 是等于空的,不等于空就是在同步队列当中
if (node.next != null) // If has successor, it must be on queue
return true;
// 遍历正个同步队列,判断node是否在同步队列当中
return findNodeFromTail(node);
}
await 方法会阻塞 ThreadA,然后 ThreadB 抢占到了锁获得了执行权限,这个时候在 ThreadB 中调用了 Condition 的 signal()方法,将会唤醒在等待队列中节点
public final void signal() {
// 判断当前线程是否获取到了锁,如果没有抛异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
// 如果首节点不为空,唤醒首节点
if (first != null)
doSignal(first);
}
doSignal
private void doSignal(Node first) {
do {
// frist的下一个节点如果为空,就把lastWaiter设置为空
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 不为空,再把first 节点从等待队列中移除
first.nextWaiter = null;
} while (!transferForSignal(first) &&
// 返回false,firstWaiter已经被从新赋值过了,如果不是空,进行下一次遍历
(first = firstWaiter) != null);
}
transferForSignal
final boolean transferForSignal(Node node) {
// 如果该节点不是condition状态(可能编程了cancelled状态),waitStatus=0,就会设置失败,返回false
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 将该节点放入到AQS队列中,返回上一个节点
Node p = enq(node);
int ws = p.waitStatus;
// 如果上一个节点状态没有被改变,也就是没有编程cancelled状态,就将该节点状态设置成singal状态
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 如果状态已经是cancelled状态,将该节点的线程挂起
LockSupport.unpark(node.thread);
return true;
}
被阻塞的线程唤醒后的逻辑
前面在分析await 方法时,线程会被阻塞。而通过 signal被唤醒之后又继续回到上次执行的逻辑中
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
checkInterruptWhileWaiting
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
transferAfterCancelledWait
进入到该方法,证明该线程被中断过。
这里有个知识点,就是进入该线程进入该方法,不一定是被signal唤醒点,也有可能是是调用了该线程的interrupt()方法,这个方法会更新一个中断标识,并且会唤醒处于阻塞状态下的线程。
final boolean transferAfterCancelledWait(Node node) {
// 如果cas成功,代表是被线程中断的,放入到AQS队列,返回true
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
// 如果cas 失败,返回false
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
如果返回true,证明这个没有执行被signal唤醒,就被中断了,checkInterruptWhileWaiting返回THROW_IE
如果返回false, 证明这个这个方法是被唤醒以后,在被中断的
checkInterruptWhileWaiting返回REINTERRUPT
reportInterruptAfterWait
如果是THROW_IE,则抛出中断异常
如果是REINTERRUPT,则重新响应中断
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
// 如果是被中断唤醒的,线程直接抛异常
if (interruptMode == THROW_IE)
throw new InterruptedException();
// 如果是被
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}