【问题标题】:How to make builder pattern thread safe in the multithreading environment?如何在多线程环境中使构建器模式线程安全?
【发布时间】:2014-02-17 01:07:15
【问题描述】:

我正在做一个项目,在该项目中我需要有我的 java 客户端的同步和异步方法。有些客户会调用同步,有些客户会根据需要调用我的java客户端的异步方法。

下面是我的 java 客户端,它有 synchronous 和 asynchronous 方法 -

public class TestingClient implements IClient {

    private ExecutorService service = Executors.newFixedThreadPool(10);
    private RestTemplate restTemplate = new RestTemplate();

    // for synchronous
    @Override
    public String executeSync(ClientKey keys) {

        String response = null;
        try {

            Future<String> handle = executeAsync(keys);
            response = handle.get(keys.getTimeout(), TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {

        } catch (Exception e) {

        }

        return response;
    }

    // for asynchronous
    @Override
    public Future<String> executeAsync(ClientKey keys) {

        Future<String> future = null;

        try {
            ClientTask ClientTask = new ClientTask(keys, restTemplate);
            future = service.submit(ClientTask);
        } catch (Exception ex) {

        }

        return future;
    }
}

现在下面是我的ClientTask 类,它实现了Callable 接口,我在ClientTask class 中使用DI 模式传递依赖关系。在调用方法中,我只是在machineIPAddress 上创建一个URL,并使用传递给ClientTask 类的ClientKeys,然后使用RestTemplate 访问服务器并获取响应 -

class ClientTask implements Callable<String> {

    private ClientKey cKeys;
    private RestTemplate restTemplate;

    public ClientTask(ClientKey cKeys, RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
        this.cKeys = cKeys;
    }

    @Override
    public String call() throws Exception {

        // .. some code here
        String url = generateURL("machineIPAddress");           
        String response = restTemplate.getForObject(url, String.class);

        return response;
    }

    // is this method thread safe and the way I am using `cKeys` variable here is also thread safe?
    private String generateURL(final String hostIPAdress) throws Exception {
        StringBuffer url = new StringBuffer();
        url.append("http://" + hostIPAdress + ":8087/user?user_id=" + cKeys.getUserId() + "&client_id="
            + cKeys.getClientId());

        final Map<String, String> paramMap = cKeys.getParameterMap();
        Set<Entry<String, String>> params = paramMap.entrySet();
        for (Entry<String, String> e : params) {
            url.append("&" + e.getKey());
            url.append("=" + e.getValue());
        }

        return url.toString();
    }
}

下面是我的 ClientKey 类使用 Builder 模式,客户将使用它来将输入参数传递给 TestingClient -

public final class ClientKey {

    private final long userId;
    private final int clientId;
    private final long timeout;
    private final boolean testFlag;
    private final Map<String, String> parameterMap;

    private ClientKey(Builder builder) {
    this.userId = builder.userId;
    this.clientId = builder.clientId;
    this.remoteFlag = builder.remoteFlag;
    this.testFlag = builder.testFlag;
    this.parameterMap = builder.parameterMap;
    this.timeout = builder.timeout;
    }

    public static class Builder {
    protected final long userId;
    protected final int clientId;
    protected long timeout = 200L;
    protected boolean remoteFlag = false;
    protected boolean testFlag = true;
    protected Map<String, String> parameterMap;

    public Builder(long userId, int clientId) {
        this.userId = userId;
        this.clientId = clientId;
    }

    public Builder parameterMap(Map<String, String> parameterMap) {
        this.parameterMap = parameterMap;
        return this;
    }

    public Builder remoteFlag(boolean remoteFlag) {
        this.remoteFlag = remoteFlag;
        return this;
    }

    public Builder testFlag(boolean testFlag) {
        this.testFlag = testFlag;
        return this;
    }

    public Builder addTimeout(long timeout) {
        this.timeout = timeout;
        return this;
    }

    public ClientKey build() {
        return new ClientKey(this);
    }
    }

    public long getUserId() {
    return userId;
    }

    public int getClientId() {
    return clientId;
    }

    public long getTimeout() {
    return timeout;
    }

    public Map<String, String> getParameterMap() {
    return parameterMap;

    public boolean istestFlag() {
    return testFlag;
    }
}

我上面的代码线程安全吗,因为我在多线程环境中使用ClientTask 类中的ClientKey 变量,所以不确定如果另一个线程尝试使ClientKey 变量同时调用TestingClient 同步会发生什么方法 -

因为客户将使用以下代码给我们打电话,他们也可以从那里给我们打电话多线程应用程序 -

IClient testClient = ClientFactory.getInstance();

Map<String, String> testMap = new LinkedHashMap<String, String>();
testMap.put("hello", "world");

ClientKey keys = new ClientKey.Builder(12345L, 200).addTimeout(2000L).parameterMap(testMap).build();

String response = testClient.executeSync(keys);

所以只是想了解我上面的代码是否是线程安全的,因为它们可以从多个线程将多个值传递给我的TestingClient 类。我感觉我的 ClientKey 类不是线程安全的,因为 parameterMap 但不确定。

我还需要StringBuffer 或StringBuilder 就可以了,因为 StringBuilder 比 StringBuffer 快,因为它不同步。

谁能帮我解决这个问题?

【问题讨论】:

  • 如果你想确保 parameterMap 在分配给 ClientKey 后不能被修改,你需要做类似:this.parameterMap = Collections.unmodifiableMap(new HashMap&lt;String, String&gt;(builder.parameterMap)) 但是如果多个线程当前正在访问一个未同步的 LinkedHashMap,你已经有了ClientKey 之外的代码中存在问题。
  • 感谢 Erwin 的建议。客户可以将不同线程的不同值传递给 TestingClient 类,这意味着他们需要使用 ClientKey 类来使输入参数始终保持不变,因为 userId 会不断变化对于他们传递的每个调用,并且与 ParameterMap 相同。LinkedHashMap 代码是客户如何从那里调用我们的示例。他们可以在那个地方有 ConcurrentHashMap,因为它在那里编码他们如何打电话给我们..

标签: java multithreading thread-safety builder


【解决方案1】:

参数ClientKey keys是给定的,所以我假设总是不同的。

我没有看到您的代码有任何同步问题,我会解释一下:

ClientTask ClientTask = new ClientTask(keys, restTemplate);
future = service.submit(ClientTask);
  • 从方法内部创建一个ClientTask 对象,该对象不在线程之间共享。
  • 使用 service.submit,返回一个 Future 对象
  • ClientTask 对象仅读取方法 generateURL 内部的键,但是,正如我之前所说,ClientKeys 对象是作为参数给出的,所以只要这个对象不被共享就可以了.

总之,代码的线程安全性取决于ExecutorService 和Future 是否是线程安全的。

更新:澄清as long as this object is not being shared

ClientKeys keys;
add keys to @keys
.. code
executeAsync(.., keys)
... code
add keys to @keys
add keys to @keys
executeAsync(.., keys)
executeAsync(.., keys)
add keys to @keys
... code
add keys to @keys
executeAsync(.., keys)

这(越来越少)我的意思是分享。由于调用了 executeAsync(),keys 正在多个线程中使用。在这种情况下,一些线程正在读取keys,而其他线程正在读取writing 数据,导致通常称为race condition。

更新 2:StringBuffer 对象是 generateURL 的本地对象(又名在其范围内),无需同步它。

【讨论】:

  • 感谢 ichramm 的建议。当人们说as long as this object is not being shared. 时,我总是感到困惑,这实际上是什么意思?因为每次调用的 Clientkey 键都会不同..
  • 那么在我的场景中你认为这会发生吗?
  • 根据你写的关于你的客户的内容,以及最后一个代码块,不,我认为这不会发生。
  • 我明白了。而且我在这里需要 StringBuffer 还是 StringBuilder 在我的generateURL 方法中会很好,因为 StringBuilder 比 StringBuffer 快,因为它不同步。
  • StringBuffer 对象是 的本地对象(也属于) generateURL,无需同步。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-23
  • 1970-01-01
  • 1970-01-01
  • 2022-11-21
  • 1970-01-01
相关资源
最近更新 更多