SocketInputStream.socketRead0 导致线程hangs的解决方案

来源:互联网 发布:程序员辛苦吗 编辑:程序博客网 时间:2024/06/05 20:39

前言


先声明,这里我没有试图去列举所有出现这种问题的情况,只针对我自己做过的项目遇到的问题。
问题代码如下:
首先通过HttpURLConnection创建链接:

        URL url = new URL( servletUrl );        HttpURLConnection connection = (HttpURLConnection)url.openConnection();        trustModifier.relaxHostChecking( connection );        connection.setDoOutput( true );        connection.setRequestMethod( REQUEST_METHOD );

然后在执行了send之后,等待远端读取,并返回,注意这里的代码,用的是URLConnection,然后从socket上读取数据,并没有用到HttpURLConnection.getResonpseCode()

    public void handleResponse( URLConnection connection ) throws IOException    {        try (BufferedReader connectionBuffer =            new BufferedReader( new InputStreamReader( connection.getInputStream(), UTF_8 ) ))        {            String response;            while( (response = connectionBuffer.readLine()) != null )            {                logger.debug( response );            }        }    }

当你的并发操作很多,线程切换很频繁,cpu load很高的时候,就会出现SocketInputStream.socketRead0的问题:

13:04:54,193 INFO  [stdout] (EJB default - 1)   at java.net.SocketInputStream.socketRead0(Native Method) ~[na:1.7.0_25]13:04:54,193 INFO  [stdout] (EJB default - 1)   at java.net.SocketInputStream.read(SocketInputStream.java:150) ~[na:1.7.0_25]13:04:54,193 INFO  [stdout] (EJB default - 1)   at java.net.SocketInputStream.read(SocketInputStream.java:121) ~[na:1.7.0_25]13:04:54,194 INFO  [stdout] (EJB default - 1)   at sun.security.ssl.InputRecord.readFully(InputRecord.java:442) ~[na:na]13:04:54,195 INFO  [stdout] (EJB default - 1)   at sun.security.ssl.InputRecord.read(InputRecord.java:480) ~[na:na]13:04:54,195 INFO  [stdout] (EJB default - 1)   at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:927) ~[na:na]13:04:54,196 INFO  [stdout] (EJB default - 1)   at sun.security.ssl.SSLSocketImpl.readDataRecord(SSLSocketImpl.java:884) ~[na:na]13:04:54,196 INFO  [stdout] (EJB default - 1)   at sun.security.ssl.AppInputStream.read(AppInputStream.java:102) ~[na:na]13:04:54,196 INFO  [stdout] (EJB default - 1)   at java.io.BufferedInputStream.fill(BufferedInputStream.java:235) ~[na:1.7.0_25]13:04:54,196 INFO  [stdout] (EJB default - 1)   at java.io.BufferedInputStream.read1(BufferedInputStream.java:275) ~[na:1.7.0_25]13:04:54,197 INFO  [stdout] (EJB default - 1)   at java.io.BufferedInputStream.read(BufferedInputStream.java:334) ~[na:1.7.0_25]13:04:54,197 INFO  [stdout] (EJB default - 1)   at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:633) ~[na:na]13:04:54,197 INFO  [stdout] (EJB default - 1)   at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:579) ~[na:na]13:04:54,197 INFO  [stdout] (EJB default - 1)   at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1322) ~[na:na]13:04:54,197 INFO  [stdout] (EJB default - 1)   at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254) ~[na:na]

原因


使用HttpURLConnection的步骤是先实例化一个URL对象,通过URL的openConnection实例化HttpURLConnection对象。然后设置参数,注意此时并没有发生连接。真正发生连接是在获得流时即conn.getInputStream这一句时,这点跟TCP Socket是一样的。并非阻塞在ServerSocket.accept()而是阻塞在获取流。所以在获取流之前应该设置好所有的参数。特别是timeout参数。

**做个总结,进行远程通信时,在客户程序中,线程在以下情况可能进入阻塞状态:**请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法时,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法或connect()方法返回。线程从Socket的输入流读入数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达输入流的末尾,或者出现了异常,才从输入流的read()方法返回或异常中断。输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类型: 1. int read():只要输入流中有一个字节,就算足够。 2. int read(byte[] buff):只要输入流中的字节数目与参数buff数组的长度相同就算足够。 3. String readLine():只要输入流中有一行字符串,就算足够。值得注意的是InputStream类并没有readLine()方法,在过滤流BufferedReader类中才有此方法。线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write()方法返回或异常中断。当调用Socket的setSoLinger()方法设置了关闭Socket的延迟时间,那么当线程执行Socket的close()方法时,会进入阻塞状态,直到底层Socket发送完所有剩余数据,或者超过了setSoLinger()方法设置的延迟时间,才从close()方法返回。在服务器程序中,线程在以下情况可能会进入阻塞状态:线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到了客户连接,才从accept()方法返回。线程从Socket的输入流读入数据时, 如果输入流没有足够的数据,就会进入阻塞状态。线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流的write()方法返回或异常中断。###解决方案---1, 通过setTimeout可以解决问题```java//设置connection timeout为3秒connection.setConnectionTimeout(3 * 1000)//设置read timeout为5秒connection.setReadTimeout(5 * 1000)2,通过一个connection的监控线程,查询定时清除掉已经expired或者idle的链接。<div class="se-preview-section-delimiter"></div>```javapublic static class IdleConnectionMonitorThread extends Thread {    private final HttpClientConnectionManager connMgr;    private volatile boolean shutdown;    public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {        super();        this.connMgr = connMgr;    }    @Override    public void run() {        try {            while (!shutdown) {                synchronized (this) {                    wait(5000);                    // Close expired connections                    connMgr.closeExpiredConnections();                    // Optionally, close connections                    // that have been idle longer than 30 sec                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);                }            }        } catch (InterruptedException ex) {            // terminate        }    }    public void shutdown() {        shutdown = true;        synchronized (this) {            notifyAll();        }    }}
0 0
原创粉丝点击