• 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!");
            }
        }
    }
  • 相关阅读:
    spark streaming 入门例子
    ElasticSearch-hadoop saveToEs源码分析
    spark 资源参数调优
    spark 任务运行原理
    spark RDD底层原理
    用实例说明Spark stage划分原理
    Spark任务提交底层原理
    spark shuffle内在原理说明
    iOS 辛格尔顿
    CodeForces 22D Segments 排序水问题
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13318096.html
Copyright © 2020-2023  润新知