关于使用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.test.common.MessageQueue.poll(MessageQueue.java:32)
	at com.test.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.test.common.MessageQueue.put(MessageQueue.java:21)
	at com.test.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!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值