近日在基于Netty写异步HttpClient的时候,需要等http连接建立并通道打开后,才能使用该连接来发送数据,但是Netty中只能等待到连接建立就会返回一个用来收发数据的channel,如果channel并没有打开,用来发送数据时就会报错,因此需要在代码中等到channel打开后再返回,想到了使用简单的wait¬ify来解决,先上一段代码:
public class HttpClient{
private Boolean connected = false;
private final class InnerHandler extends SimpleChannelUpstreamHandler {
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
synchronized(connected){
connected = true;
channel = e.getChannel();
logger.debug("{}",this);
connected.notifyAll();
}
logger.debug("channelConnected");
}
}
public void connect() throws Exception {
......InnerHandler hander = new InnerHandler();......
ChannelFuture future = bootstrap.connect();
future.await();
synchronized(connected){
if (future.isSuccess() && !connected) {
logger.debug("connection opened,waiting for channel connected!");
logger.debug("{}", hander);
connected.wait();
}
logger.debug("new http client channel established!");
}
}
}
结果程序运行到connected.wait()便死锁了,经分析发现,connected.wait()和connected.notifyAll()进行操作的不是同一个对象,因为connected = true这里将connected 重新指向了另一个对象,代码上看来connected.notifyAll()没啥问题,其实这个信号已经没人收得到了,开始的那个connected.wait()也再也得不到任何信号,会一直等下去了。
修改代码,使用一个专门的对象来做锁:
private Object lockObject = new Object();
将synchronized、wait和notifyAll使用的对象都换为lockObject,一切正常。可见,使用对象锁的时候,尽量使用一个不会被潜在改变引用地址的对象做锁,最好专门新建一个Object来做锁。
当然只要保证synchronized、wait和notifyAll使用的是同一个对象,不专门弄个Object来做锁也是可以的,为了多搞点问题出来加深印象,将代码修改了一下:
public class HttpClient{
private boolean connected = false;
private final class InnerHandler extends SimpleChannelUpstreamHandler {
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
connected = true;
channel = e.getChannel();
synchronized(this){
logger.debug("notify:{}",this);
notifyAll();
}
logger.debug("channelConnected");
}
}
public void connect() throws Exception {
......InnerHandler hander = new InnerHandler();......
ChannelFuture future = bootstrap.connect();
future.await();
if (future.isSuccess() && !connected) {
logger.debug("connection opened,waiting for channel connected!");
synchronized(hander){
logger.debug("wait:{}", hander);
hander.wait();
}
logger.debug("new http client channel established!");
}
}
}
使用hander对象来做锁,这儿的代码已经确保大家使用的都是同一个hander,运行代码,发现一切正常,再运行几次,发现又死锁了,分析debug信息:
connection opened,waiting for channel connected!
notify: com.skymobi.http.HttpClient$InnerHandler@cc52fc
channelConnected
wait: com.skymobi.http.HttpClient$InnerHandler@cc52fc
原来程序执行时先跑去notify,然后再wait了,开始时不是很清楚为啥一定要加个synchronized,只是不加的话就不能用来wait,于是囫囵吞枣的加上个synchronized。这儿看来,加上synchronized应该是获取到锁,然后修改某些状态值,供别的线程根据这些状态值去判断是否需要做某些事情,一般synchronized中如果要使用wait,都需要先判断是否满足需要wait的条件,否则就会导致死锁,而在notify的synchronized代码片段中,一般会对判断条件使用的值进行修改,然后再通知wait的线程。
最后修改过能运行的代码如下:
public class HttpClient{
private boolean connected = false;
private final class InnerHandler extends SimpleChannelUpstreamHandler {
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
synchronized(this){
connected = true;
channel = e.getChannel();
logger.debug("notify:{}",this);
notifyAll();
}
logger.debug("channelConnected");
}
}
public void connect() throws Exception {
......InnerHandler hander = new InnerHandler();......
ChannelFuture future = bootstrap.connect();
future.await();
synchronized(hander){
if (future.isSuccess() && !connected) {
logger.debug("connection opened,waiting for channel connected!");
logger.debug("wait:{}", hander);
hander.wait();
}
logger.debug("new http client channel established!");
}
}
}