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

前言


先声明,这里我没有试图去列举所有出现这种问题的情况,只针对我自己做过的项目遇到的问题。
问题代码如下:
首先通过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>

```java
public 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();
        }
    }

}

你可能感兴趣的:(Java)