【问题标题】:Multithreading (Stateless Classes)多线程(无状态类)
【发布时间】:2012-05-04 11:30:31
【问题描述】:

为长代码帖子道歉,但我想知道是否有人可以帮助解决多线程问题(我对多线程很陌生)。我正在尝试为可以与多个线程共享的 RESTFUL Web 服务 API 设计一个外观类。我正在使用 HttpURLConnection 进行连接,并使用 Google GSON 在 JSON 数据之间进行转换。

以下课程是我目前所拥有的。在这个例子中,它有一个公共方法来进行 API 调用(authenticateCustomer()),而私有方法用于促进 API 调用(即构建 POST 数据字符串、发出 POST 请求等)。

我创建了这个类的一个实例并与 1000 个线程共享它。线程调用 authenticateCustomer() 方法。大多数线程工作,但有一些线程得到一个空指针异常,这是因为我没有实现任何同步。如果我使 authenticateCustomer() 方法“同步”,它就可以工作。问题是这会导致并发性较差(例如,POST 请求突然需要很长时间才能完成,这会阻塞所有其他线程)。

现在我的问题。下面的类不是无状态的,因此是线程安全的吗?类中的极少数字段被声明为 final 并在构造函数中分配。所有方法都使用局部变量。 Gson 对象是无状态的(根据他们的网站),无论如何都会在 API 方法中创建为局部变量。

public final class QuizSyncAPIFacade 
{
    // API Connection Details
private final String m_apiDomain;
private final String m_apiContentType;
private final int m_bufferSize;

// Constructors
public QuizSyncAPIFacade()
{
    m_apiDomain      = "http://*****************************";
    m_apiContentType = ".json";
    m_bufferSize = 8192; // 8k
}

private String readInputStream(InputStream stream) throws IOException
{
        // Create a buffer for the input stream
    byte[] buffer = new byte[m_bufferSize];

    int readCount;

    StringBuilder builder = new StringBuilder();

    while ((readCount = stream.read(buffer)) > -1) {
        builder.append(new String(buffer, 0, readCount));
    }

    return builder.toString();
}

private String buildPostData(HashMap<String,String> postData) throws UnsupportedEncodingException
{
    String data = "";

    for (Map.Entry<String,String> entry : postData.entrySet()) 
    {
        data += (URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8") + "&");        
    }

    // Trim the last character (a trailing ampersand)
    int length = data.length();

    if (length > 0) {
        data = data.substring(0, (length - 1));
    }

    return data;
}

private String buildJSONError(String message, String name, String at)
{
    String error = "{\"errors\":[{\"message\":\"" + message + "\",\"name\":\"" + name + "\",\"at\":\"" + at + "\"}]}";

    return error;
}

private String callPost(String url, HashMap<String,String> postData) throws IOException
{
    // Set up the URL for the API call 
    URL apiUrl = new URL(url);

    // Build the post data
    String data = buildPostData(postData);

    // Call the API action
    HttpURLConnection conn;

    try {
        conn = (HttpURLConnection)apiUrl.openConnection();
    } catch (IOException e) {
        throw new IOException(buildJSONError("Failed to open a connection.", "CONNECTION_FAILURE", ""));
    }

    // Set connection parameters for posting data
    conn.setRequestMethod("POST");
    conn.setUseCaches(false);
    conn.setDoInput(true);
    conn.setDoOutput(true);

    // Write post data
    try {
        DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
        wr.writeBytes(data);
        wr.flush();
        wr.close();
    } catch (IOException e) {
        throw new IOException(buildJSONError("Failed to post data in output stream (Connection OK?).", "POST_DATA_FAILURE", ""));           
    }

    // Read the response from the server                
    InputStream is;

    try {
        is = conn.getInputStream();
    } catch (IOException e) {
        InputStream errStr = conn.getErrorStream();

        if (errStr != null) 
        {
            String errResponse = readInputStream(errStr);
            throw new IOException(errResponse);
        } 
        else 
        {
            throw new IOException(buildJSONError("Failed to read error stream (Connection OK?).", "ERROR_STREAM_FAILURE", ""));
        }
    }

    // Read and return response from the server
    return readInputStream(is);
}

/* -------------------------------------
 * 
 * Synchronous API calls
 * 
   ------------------------------------- */

public APIResponse<CustomerAuthentication> authenticateCustomer(HashMap<String,String> postData)
{
    // Set the URL for this API call
    String apiURL = m_apiDomain + "/customer/authenticate" + m_apiContentType;

    Gson jsonConv = new Gson();

    String apiResponse = "";

    try 
    { 
        // Call the API action
        apiResponse = callPost(apiURL, postData);

        // Convert JSON response to the required object type
        CustomerAuthentication customerAuth = jsonConv.fromJson(apiResponse, CustomerAuthentication.class);

        // Build and return the API response object
        APIResponse<CustomerAuthentication> result = new APIResponse<CustomerAuthentication>(true, customerAuth, null);

        return result;
    } 
    catch (IOException e) 
    {
        // Build and return the API response object for a failure with error list
        APIErrorList errorList = jsonConv.fromJson(e.getMessage(), APIErrorList.class);

        APIResponse<CustomerAuthentication> result = new APIResponse<CustomerAuthentication>(false, null, errorList);

        return result;
    }
}

}

【问题讨论】:

  • 这个类绝对是线程安全的
  • 您直接使用传入的 HashMap(无副本) - 如果在您使用它时它被另一个线程修改,则可能会导致您描述的问题。我会先把它复制到一个局部变量中,然后检查局部变量的内容是否有效,然后再使用局部变量。
  • 如何确定 NPE 是由多线程引起的?哪一行抛出 NPE,stackTrace 是什么?
  • 我们真的需要更多信息。 authenticateCustomer 不能返回空指针。您是说返回的APIResponse 中的CustomerAuthentication 为空,因为您在authenticateCustomer 中遇到异常?
  • 谢谢你们。我认为问题是线程问题,因为当我同步 authenticateCustomer() 方法时,它每次都有效(尽管并发性很差)。事实证明,我按照下面的答案超载了我的身份验证服务,并且没有正确使用它。这导致我的工作线程中出现 NPE。您的回答让我放心,我的课堂没有根本性的问题。

标签: java multithreading stateless


【解决方案1】:

如果您收到错误,可能是因为您正在重载身份验证服务(如果您一次执行此操作,则不会发生这种情况)也许它会返回类似 500、503 或 504 的错误,您可能是忽略并得到任何您期望的结果,您返回 null http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

假设您没有 1000 cpu,我会使用更少的线程,拥有这么多线程可能会更慢而不是更高的效率。

我还会检查您的服务是否每次都正确返回,并调查您获得null 值的原因。

如果您的服务一次只能处理 20 个请求,您可以尝试使用 Semaphore 作为最后的手段。这可以用来限制并发请求的数量。

【讨论】:

  • 在这种情况下,使用比 CPU 内核更多的线程实际上是有意义的,因为它们可能不得不大部分时间等待 I/O。虽然当然值得考虑一种基于非阻塞 I/O 的方法。
  • 这是正确的,前提是通过有更多并发请求来提高服务或 IO 的吞吐量。虽然某些并发性通常会提高吞吐量,但在某些情况下,最优值可能相当小。显然,尝试执行超出服务处理能力的请求可能会导致失败。
  • 就是彼得,谢谢!我正在超载我的身份验证服务。我创建的线程越多,返回错误的百分比就越大。 NPE 是由于我没有在工作线程中正确验证来自服务的响应,而不是线程安全问题。一旦我修复了验证,我发现一旦超过一定数量的工作线程,我就会收到大约 500 个错误。
【解决方案2】:

任何无状态类本质上都是线程安全的,只要它访问的对象要么是线程私有的,要么本身是线程安全的。

【讨论】:

    猜你喜欢
    • 2011-04-04
    • 2021-10-17
    • 2021-10-15
    • 2022-01-10
    • 1970-01-01
    • 2011-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多