zoukankan      html  css  js  c++  java
  • 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!");
            }
        }
    }
  • 相关阅读:
    Python:给你们安排一波VIP音乐,看我是如何不充会员也能下载的
    最详细Python打包exe教程,并修改图标,30秒搞定!
    Python:20行代码爬取高质量帅哥美女视频,让你一次看个够
    【Python爬虫】招聘网站实战合集第一弹:爬取前程无忧
    Python爬虫:爬点大家都喜欢的东西,比如美女!每天保持心情愉悦!
    Python吃喝玩乐:爬取全城按摩门店,看看有没有你想去的!
    明天就是1024了,Python前来报到!爬取全网M子图片!
    周末福利!用Python爬取美团美食信息,吃货们走起来!
    Python小工具:据说这是搜索文件最快的工具!没有之一!一起感受下......
    简单实现一个流程图(箭头流程图)
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13318097.html
Copyright © 2011-2022 走看看