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!");
            }
        }
    }
  • 相关阅读:
    jhljx跑跑跑(找规律)
    FFT教你做乘法(FFT傅里叶变换)
    寻找最远点对(凸包求解)
    捡火柴的Nova君(n个线段相交问题)
    生命游戏/Game of Life的Java实现
    如果看了此文你还不懂傅里叶变换,那就过来掐死我吧【完整版】(转)
    北航第十一届程序设计竞赛网络预赛题解
    LeetCode 3 Longest Substring Without Repeating Characters(最长不重复子序列)
    UVa 112
    LeetCode 2 Add Two Numbers(链表操作)
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13318097.html
Copyright © 2011-2022 走看看