【问题标题】:How to prevents read from happening whenever I am doing a write?每当我进行写入时,如何防止读取发生?
【发布时间】:2014-04-28 19:07:37
【问题描述】:

我正在尝试实现锁定,我不希望在写入时发生读取。

下面是我使用CountDownLatchClientData 类-

public class ClientData {

    private static final AtomicReference<Map<String, Map<Integer, String>>> primaryMapping = new AtomicReference<>();
    private static final AtomicReference<Map<String, Map<Integer, String>>> secondaryMapping = new AtomicReference<>();
    private static final AtomicReference<Map<String, Map<Integer, String>>> tertiaryMapping = new AtomicReference<>();

    // should this be initialized as 1?
    private static final CountDownLatch hasBeenInitialized = new CountDownLatch(1) 

    public static Map<String, Map<Integer, String>> getPrimaryMapping() {
        try {
            hasBeenInitialized.await();
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }

        return primaryMapping.get();
    }

    public static void setPrimaryMapping(Map<String, Map<Integer, String>> map) {
        primaryMapping.set(map);
        hasBeenInitialized.countDown();
    }

    public static Map<String, Map<Integer, String>> getSecondaryMapping() {
        try {
            hasBeenInitialized.await();
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }

        return secondaryMapping.get();
    }       

    public static void setSecondaryMapping(Map<String, Map<Integer, String>> map) {
        secondaryMapping.set(map);
        hasBeenInitialized.countDown();
    }

    public static Map<String, Map<Integer, String>> getTertiaryMapping() {
        try {
            hasBeenInitialized.await();
        } catch (InterruptedException e) {
            throw new IllegalStateException(e);
        }

        return tertiaryMapping.get();
    }       

    public static void setTertiaryMapping(Map<String, Map<Integer, String>> map) {
        tertiaryMapping.set(map);
        hasBeenInitialized.countDown();
    }       
}

问题陈述:-

我需要等待上面代码中的三个AtomicReferencesget 调用。一旦对我的三个 AtomicReferencesset 调用完成了所有写入,那么我将允许调用我拥有的三个 getter。

所以我决定使用CountDownLatch,我已经初始化为1?我需要将其初始化为3吗?并且每次在我对新更新进行第一次设置之前,我是否需要将倒计时闩锁重新设置回 3?因为我将在单独的三个语句中设置这三个 AtomicReferences

我猜我上面的代码有问题?

注意:-

我将在其他班级进行这样的设置 -

ClientData.setPrimaryMapping(primaryTables);
ClientData.setSecondaryMapping(secondaryTables);
ClientData.setTertiaryMapping(tertiaryTables);

一旦设置了这些AtomicReferences,其他一些线程必须从这些AtomicReferences中读取数据。

更新:-

下面是我的后台线程代码,它将从 URL 中获取数据,对其进行解析并将其存储在 ClientDataclass 变量中。

public class TempScheduler {

    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        public void startScheduler() {
            final ScheduledFuture<?> taskHandle = scheduler.scheduleAtFixedRate(new Runnable() {
                public void run() {
                try {
                    callServers();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                }
            }, 0, 10, TimeUnit.MINUTES);
        }
    }

    // call the servers and get the data and then parse 
    // the response.
    private void callServers() {
        String url = "url";
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject(url, String.class);
        parseResponse(response);

    }

    // parse the response and store it in a variable
    private void parseResponse(String response) {
        //...       
        ConcurrentHashMap<String, Map<Integer, String>> primaryTables = null;
        ConcurrentHashMap<String, Map<Integer, String>> secondaryTables = null;
        ConcurrentHashMap<String, Map<Integer, String>> tertiaryTables = null;

        //...

        // store the data in ClientData class variables which can be
        // used by other threads
    ClientData.setPrimaryMapping(primaryTables);
    ClientData.setSecondaryMapping(secondaryTables);
    ClientData.setTertiaryMapping(tertiaryTables);
    }
}

【问题讨论】:

  • 在这里使用synchronized关键字不是更容易更易读吗?
  • 请张贴入口点方法的代码main
  • 你考虑过ReadWriteLock吗?这正是它的设计目的。
  • 如果ClientData.setPrimaryMapping(primaryTables);同时被调用3次呢?
  • @Braj:肯定不会同时调用 3 次。我已经更新了设置这三个 AtomicReference 的代码。

标签: java multithreading countdownlatch atomicreference


【解决方案1】:

如果您想独立处理所有 3 个变量(即获得第三个变量不需要等待设置主要变量),这就是我阅读您的问题的方式,您只需为每个地图创建 1 个倒计时锁存器。每个设置器对正在设置的变量的相应锁存器进行倒计时。每个 getter 在各自的锁存器上调用 await。

【讨论】:

  • 就我而言,如果setPrimaryMapping 发生任何写入,我需要等待getPrimaryMapping。同样,如果setSecondaryMapping 发生任何写入,我需要等待getSecondaryMapping。另外,如果setTertiaryMapping 发生任何写入,我需要等待getTertiaryMapping。那么在这种情况下,它也应该设置为 1?如果是,那么您能仅就我所知解释一下原因吗?
  • 每个映射都需要一个倒计时锁存器(因为它们都是独立跟踪的)。这意味着 3 个倒计时锁存器的值都为 1。该值必须为 1,因为您只等待一个倒计时事件发生。
  • 现在说得通了。有什么办法,我可以有一个倒计时锁存器而不是三个?我想为此,我可能需要更改构建 ClientData 对象的方式。对吗?
  • 如果你想独立地跟踪 3 个事件,则需要 3 个锁存器。如果您更改为使用单个 setter 方法来获取所有 3 个映射(直接或使用 @bizclop 建议的包装类),那么在设置后您只需要一个锁存器即可倒计时。
【解决方案2】:

这种设置在 IMO 上完全是矫枉过正。

这是一个可以正常工作并且更简单的替代方法:

public class MappingContainer {
     private final Map<String, Map<Integer, String>> primaryMapping;
     private final Map<String, Map<Integer, String>> secondaryMapping;
     private final Map<String, Map<Integer, String>> tertiaryMapping;

     // + constructor and getters
}

public class ClientData {
    private static volatile MappingContainer mappingContainer;

    // regular setters and getters
}

public class TempScheduler {
//...
    private void parseResponse(String response) {
        //...       
        ConcurrentHashMap<String, Map<Integer, String>> primaryTables = null;
        ConcurrentHashMap<String, Map<Integer, String>> secondaryTables = null;
        ConcurrentHashMap<String, Map<Integer, String>> tertiaryTables = null;

        //...

        // store the data in ClientData class variables which can be
        // used by other threads
        ClientData.setMappingContainer( new MappingContainer( primaryTables, secondaryTables, tertiaryTables );
    }
}

锁存器和原子引用应该留给更简单的结构无法解决的问题。特别是,如果您必须对任何 N 个事件(而不是 3 个特定事件)进行计数,则锁存器非常有用,而原子引用仅在您使用 compare-and-set 或 get-and-set 习惯用法时才有用。

【讨论】:

  • 谢谢。在您的示例中,Volatile 在这里有何帮助?假设如果我启动我的主应用程序,那么它会等待 get 调用吗?因为 set call 可能需要一些时间,因为它是由后台线程完成的,所以我们不知道解析响应并在 ClientData 类中设置它需要多少时间?那么在这种情况下,first get call 会失败对吧?
  • 我明白了,那有点不同。如果要在调用 setter 之前阻止 getter,请使用闩锁而不是 volatile 关键字。只有一个锁存器,从 1 开始倒数(因为您希望设置器被调用一次)。一个更好的解决方案是仅在所有表都可用时构造 ClientData 对象本身。
  • 至少我们达成了一致。 :)。就我而言,因为我从后台线程对每个表都有三个不同的集合调用,如上所示。那么,在那种情况下,countdownlatch 应该是1 吗?或者你是说,我应该只在所有表都可用时构造ClientData对象,然后在这种情况下,我可以使用初始化为1的锁存器?
  • 如果在所有表都可用时构造ClientData,则根本不需要闩锁,甚至不需要任何其他机制。每个构造函数调用结束时都有一个隐式内存屏障,确保在其中初始化的 final 字段对每个人都是可见的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 2012-09-13
  • 2020-04-11
  • 2019-11-13
  • 1970-01-01
  • 2020-04-01
  • 2015-08-05
相关资源
最近更新 更多