关于使用synchronized (非this对象) 时的注意事项
摘要
记录一个亲身经历的关于synchronized使用时的一次错误,直接上代码吧。
代码
public class MessageQueue {
private Queue<Message> queue;
private int waiter = 0;
private final Object lock = new Object();
public MessageQueue() {
this.queue = new LinkedList<Message>();
}
public void put(Message message) {
// 这里使用非this对象对消息存放方法上锁
synchronized (lock) {
queue.add(message);
if (waiter > 0) {
waiter--;
notifyAll();
}
}
}
public Message poll() {
Message message = null;
// 这里使用非this对象对消息取出方法上锁
synchronized (lock) {
while ((message = queue.poll()) == null) {
try {
waiter++;
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return message;
}
}
这是一段异步消息队列的java实现,代码中,使用非this对象对消息队列的存取操作上锁,当没有消息时,若发生线程取出消息,调用wait()方法使该线程进入等待状态。
看起来好像没什么错误,但我们执行一下。
public class Main {
public static void main(String[] args) {
MessageQueue messageQueue = new MessageQueue();
new Thread(() -> {
int i = 0;
while (true) {
messageQueue.put(new Message(i + "", String.format("第%s条消息", i++)));
}
}).start();
new Thread(() -> {
while (true) {
Message message = messageQueue.poll();
}
}).start();
}
}
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.souche.im.server.common.MessageQueue.poll(MessageQueue.java:32)
at com.souche.im.server.Server.lambda$main$1(Main.java:12)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.notifyAll(Native Method)
at com.souche.im.server.common.MessageQueue.put(MessageQueue.java:21)
at com.souche.im.server.Server.lambda$main$0(Main.java:7)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
抛出了java.lang.IllegalMonitorStateException异常。
关于java.lang.IllegalMonitorStateException异常这里简单介绍一下,这个异常主要在调用wait()/notify()/notifyAll()时会抛出,原因是wait()/notify()/notifyAll()的调用必须在该线程持有该对象的对象锁时才能调用,然后释放对象锁,如果线程未持有该对象锁就调用,则会抛出此异常。
注意上面这段话的加粗部分,其实原因也许有些人应该已经知道了,结合到这段代码解释报错原因就是:
此处在MessageQueue实例中调用wait()/notifyAll()方法,则说明调用线程必须先获得MessageQueue实例的对象锁,然而这里上锁的并非是MessageQueue实例(即this),而是非this对象,故抛出java.lang.IllegalMonitorStateException异常。
总结
当初自己学习synchronized关键字和wait/notify时,由于理解偏差,将wait/notify的使用时机总结为:在synchronized代码块或方法体中调用就没问题了。直到踩到坑,才返回来再去琢磨总结。也曾看到有些文章对此也做出类似如上的错误或者含糊的总结,如这篇博文:
画红部分为作者对此做的简单解释,表意比较含糊,很容易让阅读的人理解为给任意对象上锁就行了(并不是指作者解释错误带偏读者理解哈~),所以特发此博(网上也找了很多文章,没有针对这个具体指出来的),希望大家以后在异步编程方面少走弯路。
QAQ!