Java wait/notify中的坑

近日在基于Netty写异步HttpClient的时候,需要等http连接建立并通道打开后,才能使用该连接来发送数据,但是Netty中只能等待到连接建立就会返回一个用来收发数据的channel,如果channel并没有打开,用来发送数据时就会报错,因此需要在代码中等到channel打开后再返回,想到了使用简单的wait&notify来解决,先上一段代码:


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!");
        }
    }
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注