在上文"浅谈Java同步锁"中,只对同步锁的相关概念做了简单阐述,没给出应用实例。本文,将结合现实中对于同步锁的需求,编个小例子。标题比较大,而实例并不一定十分恰当,请各位见谅。
需求: 一个消费者在不停的从queue里取消息,当没有消息时,阻塞等待,直到有消息来时,消费它。
简析: 乍一看,我们完全可以写个循环不断的去读它(queue),直到该queue不再empty,则消费一条消息(ps: 设个标志位, 循环去读,同理)。可是,无限的循环不仅有可能浪费资源,而且如果额外想设个timeout的话,还要多写好多代码,如需要取当前时间,进行比对等等。这时,如果我们使用wait()或wait(int timeout)让其阻塞等待,直到有其他线程执行了notify(),则可以让问题迎刃而解。
废话不多说,首先是我们的Consumer类。
import java.util.LinkedList;
/**
* Consumer. <BR>
*
* @author Michael Ma
* @version 1.0.0
*/
public class Consumer {
/** A lock object */
private final Object lock;
/**queue of message */
private LinkedList<Object> msgQueue = new LinkedList<Object>();
/** CLOSED message */
public static final Object CLOSED = new Object();
/**
* 构造函数. <BR>
* lock指向this指针
*
*/
public Consumer() {
this.lock = this;
}
/**
* 开始消费. 如果queue中没有消息,则阻塞 <BR>
*
* @return this
*/
public Consumer startConsume() {
synchronized (lock) {
try {
if (!msgQueue.isEmpty()) {
return this;
}
// 将timeout设为10秒
lock.wait(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return this;
}
/**
* 从queue中读消息 <BR>
*
* @return message content
* @throws Exception e
*/
public Object getMessage() throws Exception{
synchronized (lock) {
// 从queue中取一条消息
Object message = msgQueue.poll();
// 若该消息为Error, 抛出
if (message instanceof Exception) {
throw (Exception) message;
}
// 返回null如果接到CLOSED消息,或message本身为空
if (message == null
|| message == CLOSED) {
return null;
}
return message;
}
}
/**
* 向Queue里添加消息, notfify等待中的线程 <BR>
*
* @param message Object
*/
public void setMessage(Object message) {
synchronized (lock) {
// 向Queue中添加消息
msgQueue.add(message);
// notify等待中的线程
lock.notify();
}
}
/**
* 关闭消息 <BR>
*/
public void setClosed() {
setMessage(CLOSED);
}
}
测试类, 比较简单,就不加注释啦
public class ConsumerTest extends Thread {
Consumer c = null;
public ConsumerTest(Consumer c) {
this.c = c;
}
public static void main(String[] args) throws Exception {
Consumer c = new Consumer();
// 开一个线程,去往queue里添加消息
ConsumerTest test = new ConsumerTest(c);
test.start();
Object msg;
while ((msg = c.startConsume().getMessage()) != null) {
System.out.println(msg);
}
}
public void run() {
try {
sleep(3000);
c.setMessage(new String("abc"));
sleep(2000);
c.setMessage(new String("def"));
c.setMessage(Consumer.CLOSED);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
小结: 开始编这个例子时,还是挺容犯些小错误的。
1. 首先应该注意synchronized的对象,和wait,notify的对象,是不是相同的, 否则会报llegalMonitorStateException异常。 (解释请参考上文)。
2. 各不同线程操作的对象是不是一个,若notify该类的其他对象则是没用的,当然,如果该类被设计为单体类,就没有这个后顾之忧啦。
3. notifyAll是唤醒在该对象上所有在waiting中的线程,适当场合时应用。(例如一个公告牌,很多线程都在等最新的消息,当公告消息来时,唤醒所有读它的线程。)
* 题外话: 在Java和其他一些开源项目中,如Mina,会经常碰到类似的应用,想了解它,还是自己编个小程序,做测试吧,相信会有所收获!