【JavaSE】多线程(9)_Condition的等待/通知机制

本篇总结Lock体系下的Condition的实现原理,以及它的await和signal等待/通知机制 ~

之前我们学到 Object的wait和notify/notifyAll是与对象监视器monitor配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别,后者是语言级别,具有 更高的可控制性和扩展性。

1 Condition与Object对比

除了在使用方式上有不同之外,在功能特性上也有很大不同:

1 Condition支持不响应中断,Object方式不支持;
2 Condition支持多个等待队列(new多个Condition对象),而Object方式只支持一个;
3 Condition支持超时时间设置,Object不支持。

通过参照Object的wait和notify/notifyAll方法,Condition提供的方法如下:

  • 等价wait的方法(等待):
    void await() throws InterruptedException:当前线程进入等待态,若其他线程调用condition的signal或signalAll方法并且当前线程获取Lock,则从await方法返回;若在等待状态中被中断就抛出异常。
    long awaitNanos(long nanosTimeout):当前线程进入等待状态知道被通知,中断或超时。
    boolean await(long time,TimeUnit unit)throws InterruptedException:同上,支持自定义时间单位。
    boolean awaitUntil(Date deadline)throws InterruptedException:当前线程进入等待状态直到被通知,中断或到了某个时间。
    
  • 等价notify的方法(通知):
    void signal():唤醒一个等待在condition上的线程,将该线程从等待队列中转移到同步队列中,若在同步队列中能竞争到Lock则可从等待方法中返回。
    void signalAll():能唤醒所有等待在condition上的线程。
    

2 Condition实现原理

  • 由condition的源码可知,通过lock.newcondition()创建一个condition对象,此方法实际上会new一个ConditionObject对象,而ConditionObject类是AQS的一个内部类。
  • ConditionObject类中有两个成员变量;该类通过持有等待队列的头尾指针来管理队列。
    private transient Node firstWaiter;
    private transient Node lastWaiter;
    
  • 其中Node类复用了在AQS中的Node类,有个属性:Node nextWaiter;
  • condition内部维护一个等待队列,是不带头节点的单向队列,所有调用condition.await()方法的线程都会加入其中,并且线程状态转换为等待状态。
  • 等待队列可多次调用lock.newcondition()方法创建多个condition对象,也就是一个lock可持有多个等待队列。
  • Object方式在Object对象监视器上只能拥有一个同步队列和一个等待队列,而并发包中的Lock拥有一个同步队列和多个等待队列。

有界队列:当队列为空时,队列的获取(删除)操作将会阻塞获取(删除)线程,直到队列中有新增元素;当队列已满时,队列的插入操作将会阻塞插入线程,直到队列出现空位。
应用Condition实现有界队列如下:

public class BoundedQueue<T> {
	private Object[] items;
	// 队列中当前元素个数
	private int count;
	private Lock lock = new ReentrantLock();
	private Condition empty = lock.newCondition();
	private Condition full = lock.newCondition();
	public BoundedQueue(int size) {
		items = new Object[size];
	}
	// 添加元素方法,如果当前队列已满,则添加线程进入等待状态,直到有空位被唤醒
	public void add(T t,int addIndex) throws InterruptedException {
		lock.lock();
		try {
			// 当前队列已满,添加线程进入等待状态
			while (count == items.length) {
			full.await();
		}
		items[addIndex] = t;
		count++;
		empty.signal();
		}finally {
			lock.unlock();
		}
	}
	// 删除元素方法,如果当前队列为空,则移除线程进入等待状态直到队列不为空时被唤醒
	public T remove(int removeIndex) throws InterruptedException {
		lock.lock();
		try {
			// 当队列为空时,移除线程进入等待状态
			while (count == 0) {
			empty.await();
			}
			Object x = items[removeIndex];
			count--;
			full.signal();
			return (T) x;
		}finally {
			lock.unlock();
		}
	}
}

3 await实现原理

源码如下:

public final void await() throws InterruptedException {
	if (Thread.interrupted())
		throw new InterruptedException();
	// 1.将当前线程包装为Node,尾插到等待队列中
	Node node = addConditionWaiter();
	// 2.释放当前线程所占用的lock,释放后会唤醒同步队列中的下一个节点
	int savedState = fullyRelease(node);
	int interruptMode = 0;
	while (!isOnSyncQueue(node)) {
		// 当前节点不在同步队列时被阻塞进入等待状态
		LockSupport.park(this);
		if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
			break;
	}
	// 被唤醒后进入同步队列自旋竞争同步状态
	if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
		interruptMode = REINTERRUPT;
	if (node.nextWaiter != null) // clean up if cancelled
		unlinkCancelledWaiters();
	// 处理中断状况
	if (interruptMode != 0)
		reportInterruptAfterWait(interruptMode);
}
  • 调用condition.await()方法后会使当前获取lock的线程进入到等待队列,直至被signal/signalAll后使当前线程从等待队列中移至到同步队列中,直到获得lock后才会从await方法返回,或在等待时被中断做中断处理。
  • 1 调用addConditionWaiter()将当前线程用尾插的方式将封装的Node插入到等待队列中。该方法源码如下:
private Node addConditionWaiter() {
	Node t = lastWaiter;
	if (t != null && t.waitStatus != Node.CONDITION) {
		unlinkCancelledWaiters();
		t = lastWaiter;
	}
	// 将当前线程包装为Node
	Node node = new Node(Thread.currentThread(), Node.CONDITION);
	if (t == null)
		firstWaiter = node;
	else
		// 尾插入等待队列
		t.nextWaiter = node;
	lastWaiter = node;
	return node;
}
  • 2 将当前节点插入到等待队列后,由fullyRelease()方法使当前线程释放lock,源码如下:
final int fullyRelease(Node node) {
	boolean failed = true;
	try {
		int savedState = getState();
		// 调用AQS的释放同步状态方法release()
		if (release(savedState)) {
			// 成功释放锁状态
			failed = false;
			return savedState;
		} else {
			// 释放同步状态失败抛出异常
			throw new IllegalMonitorStateException();
		}
	} finally {
		// 释放同步状态失败后将当前节点状态置为取消状态
		if (failed)
			node.waitStatus = Node.CANCELLED;
	}
}
  • 上述代码调用AQS的模板方法release方法释放AQS的同步状态并且唤醒在同步队列中头结点的后继节点引用的线程。若释放成功则正常返回,若释放失败就抛出异常。

  • 3 当前线程被中断或调用condition.signal/signalAll()方法,使当前节点移动到同步队列,这是退出await方法的前提。当退出while循环后就会调用acquireQueued(node,savedState),在自旋过程不断尝试获取同步状态,直至成功,即必须是已经获得了condition引用(关联)的lock。

4 signal/signalAll实现原理

  • 调用condition的signal或signalAll方法可将等待队列中等待时间最长的节点移动到同步队列中,使该节点有机会获得lock。
  • 因为队列先进先出特性,每次调用signal方法定是将头节点移动到同步队列中。
public final void signal() {
	// 当前线程是否已经获取lock
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	// 获取等待队列的第一个节点,之后的操作都是针对这个节点
	Node first = firstWaiter;
	if (first != null)
		doSignal(first);
}
  • 其中doSignal方法:
private void doSignal(Node first) {
	do {
		if ( (firstWaiter = first.nextWaiter) == null)
			lastWaiter = null;
		// 将头结点从等待队列中移除
		first.nextWaiter = null;
		// transferForSignal方法对头结点做真正的处理
	} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
  • 其中transferForSignal方法:
final boolean transferForSignal(Node node) {
	// 首先将节点状态更新为0
	if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
		return false;
	// 将节点使用enq方法尾插到同步队列中
	Node p = enq(node);
	int ws = p.waitStatus;
	if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
		LockSupport.unpark(node.thread);
	return true;
}
  • 结论:调用condition的signal的前提是当前线程已获取lock,该方法会使等待队列中的头节点移入到同步队列,移入后才有机会使得等待线程被唤醒,即从await方法中的LockSupport.park(this)方法中返回,从而有机会使调用await方法的线程成功退出。

5 Condition机制实现生产者-消费者模型

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Goods {
	// 商品名称
	private String name;
	// 当前商品数量
	private int count;
	// 商品最大数量
	private int maxCount;
	public Goods(int maxCount) {
		this.maxCount = maxCount;
	}
	private Lock lock = new ReentrantLock();
	private Condition consumer = lock.newCondition();
	private Condition producer = lock.newCondition();
	// 生产方法
	public void setGoods(String name) {
		lock.lock();
		try {
			// 当商品数量达到了最大值时阻塞生产者线程
			while (count==maxCount) {
				System.out.println(Thread.currentThread().getName()+"商品数量已达最大,等待消费者消费");
				producer.await();
			}
			Thread.sleep(200);
			// 生产商品
			this.name = name;
			count++;
			System.out.println(Thread.currentThread().getName()+"生产"+toString());
			// 唤醒消费者线程
			consumer.signalAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	// 消费方法
	public void getGoods() {
		lock.lock();
		try {
			// 商品数为0时阻塞消费者线程
			while (count == 0) {
			System.out.println(Thread.currentThread().getName()+"商品已被消费完,等待生产者生产");
			consumer.await();
			}
			Thread.sleep(200);
			// 消费商品
			count--;
			System.out.println(Thread.currentThread().getName()+"消费"+toString());
			// 唤醒生产者线程
			producer.signalAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	@Override
	public String toString() {
		return "Goods{" +"name='" + name + '\'' +", count=" + count +'}';
	}
}
class Producer implements Runnable {
	private Goods goods;
	public Producer(Goods goods) {
		this.goods = goods;
	}
	@Override
	public void run() {
		while (true) {
			this.goods.setGoods("纪梵希小羊皮口红套装");
		}
	}
}
class Consumer implements Runnable {
	private Goods goods;
	public Consumer(Goods goods) {
		this.goods = goods;
	}
	@Override
	public void run() {
		while (true) {
			this.goods.getGoods();
		}
	}
}
public class Test {
	public static void main(String[] args) {
		List<Thread> list = new ArrayList<>();
		Goods goods = new Goods(10);
		Producer producer = new Producer(goods);
		Consumer consumer = new Consumer(goods);
		// 创建10个消费者线程
		for (int i = 0;i < 10;i++) {
			Thread thread = new Thread(consumer,"消费者"+i);
			list.add(thread);
		}
		// 创建5个生产者线程
		for (int i = 0;i < 5;i++) {
			Thread thread = new Thread(producer,"生产者"+i);
			list.add(thread);
		}
		for (Thread th : list) {
			th.start();
		}
	}
}

你可能感兴趣的:(Java)