java - 如何在使用RentrantLock完成所有三个 map 的初始化之前避免读取,并在更新完成后返回更新的 map 集?

标签 java multithreading thread-safety immutability reentrantlock

我正在尝试实现锁定,通过该锁定我想避免在对三个映射进行写操作时发生读取。所以我的要求是-

  • 读取块,直到第一次设置了所有三个 map 。
  • 现在第二次,如果我要更新 map ,我仍然可以返回所有三个旧 map 值(在对所有三个 map 进行更新之前),或者当更新为在所有三张 map 上都完成了。

  • 因为我有三个Maps-primaryMappingsecondaryMappingtertiaryMapping,所以它应该返回三个更新后的 map 的所有新值,或者应该返回 map 的所有旧值。基本上,在更新时,我不想返回具有旧值的primaryMapping,具有新值的secondaryMapping和具有新值的tertiaryMapping

    它应该是一致的,要么应该返回旧值,要么应该在更新 map 后返回新值。就我而言, map 的更新将每三个月或四个月进行一次。

    下面是我的ClientData类,在其中我使用的是整个逻辑都存在的ReentrantLock-
    public class ClientData {
    
        private static final class MapContainer {
            private Map<String, Map<Integer, String>> value = null;
    
            public Map<String, Map<Integer, String>> getValue() {
                return value;
            }
    
            public void setValue(Map<String, Map<Integer, String>> value) {
                this.value = value;
            }
        }
    
        private static final MapContainer primaryMapping = new MapContainer();
        private static final MapContainer secondaryMapping = new MapContainer();
        private static final MapContainer tertiaryMapping = new MapContainer();
        private static final MapContainer[] containers = {primaryMapping, secondaryMapping, tertiaryMapping};
        private static boolean allset = false;
        private static final Lock lock = new ReentrantLock();
        private static final Condition allsetnow = lock.newCondition();
    
        private static final Map<String, Map<Integer, String>> getMapping(MapContainer container) {
            lock.lock();
            try {
                while (!allset) {
                    allsetnow.await();
                }
                return container.getValue();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // reset interruptedd state.
                throw new IllegalStateException(e);
            } finally {
                lock.unlock();
            }
    
        }
    
        public static void setAllMappings(Map<String, Map<Integer, String>> primary,
                Map<String, Map<Integer, String>> secondary,
                Map<String, Map<Integer, String>> tertiary) {
            lock.lock();
            try{
    
                // how  to avoid this?
                if (allset) {
                    throw new IllegalStateException("All the maps are already set");
                }
    
                primaryMapping.setValue(primary);
                secondaryMapping.setValue(secondary);
                tertiaryMapping.setValue(tertiary);
                allset = true;
                allsetnow.signalAll();
            } finally {
                lock.unlock();
            }
        }       
    
    
        public static Map<String, Map<Integer, String>> getPrimaryMapping() {
            return getMapping(primaryMapping);
        }
    
        public static Map<String, Map<Integer, String>> getSecondaryMapping() {
            return getMapping(secondaryMapping);
        }
    
        public static Map<String, Map<Integer, String>> getTertiaryMapping() {
            return getMapping(tertiaryMapping);
        }       
    }
    

    下面是我的后台线程代码,它将从我的服务URL中获取数据,并在我的应用程序启动后每10分钟保持运行,然后它将解析来自该URL的数据并将其存储在ClientData类变量中在那三张 map 中。
    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 if anything has changed  
            // which can be used by other threads
            if(changed) {
                ClientData.setAllMappings(primaryTables, secondaryTables, tertiaryTables);
            }
        }
    }
    

    我将在我的主要应用程序的主线程中使用getPrimaryMapping类中的getSecondaryMappinggetTertiaryMappingClientData,因此我想返回这三个映射中的所有新值集,或者如果发生更新,则阻止它并返回所有更新完成后,这三个 map 的新值集。

    问题陈述:-

    在上面的ClientData类中所示的代码库中,我想,第一次设置后,我将无法更新 map ,因为此行会引起问题,并且会引发异常,然后引发异常实现我的第二点,如上所示?
    // how  to avoid this?
    if (allset) {
        throw new IllegalStateException("All the maps are already set");
    }
    

    如何成功实现以上两点?我想,这里有些不重要的事情我想念吗?我想在这里使用ReentrantLock,但也欢迎其他任何建议。我主要关心的是性能问题。因为我将每三个月在这三张 map 上进行一次设置,所以很好。但是每秒每1000个请求中的主应用程序代码就会出现三张 map ,所以我想变得非常快。

    最初,我正在考虑删除此if语句-
    // how  to avoid this?
    if (allset) {
        throw new IllegalStateException("All the maps are already set");
    }
    

    但是我怀疑,这将无法正常工作,因为在我更新映射时,线程之间的映射会不匹配吗?

    这就是我从主应用程序线程中从ClientData class读取值的方式-
    String data1 = ClientData.getPrimaryMapping().get(some_value1).get(some_value2);
    String data2 = ClientData.getSecondaryMapping().get(some_value1).get(some_value3);
    String data3 = ClientData.getTertiaryMapping().get(some_value1).get(some_value4);
    

    更新:-

    满足以上所有条件的另一种使用CountDownLatch的解决方案-

    以下是我在其中使用ClientDataCountDownLatch类-
    public class ClientData {
    
        public static class Mappings {
            public final Map<String, Map<Integer, String>> primary;
            public final Map<String, Map<Integer, String>> secondary;
            public final Map<String, Map<Integer, String>> tertiary;
    
            public Mappings(
                Map<String, Map<Integer, String>> primary,
                Map<String, Map<Integer, String>> secondary,
                Map<String, Map<Integer, String>> tertiary
            ) {
                this.primary = primary;
                this.secondary = secondary;
                this.tertiary = tertiary;
            }
        }
    
        private static final AtomicReference<Mappings> mappings = new AtomicReference<>();
        private static final CountDownLatch hasBeenInitialized = new CountDownLatch(1);
    
        public static Mappings getMappings() {
            try {
                hasBeenInitialized.await();
                return mappings.get();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new IllegalStateException(e);
            }
        }
    
        public static void setMappings(
            Map<String, Map<Integer, String>> primary,
            Map<String, Map<Integer, String>> secondary,
            Map<String, Map<Integer, String>> tertiary
        ) {
            setMappings(new Mappings(primary, secondary, tertiary));
        }
    
        public static void setMappings(Mappings newMappings) {
            mappings.set(newMappings);
            hasBeenInitialized.countDown();
        }
    }
    

    我将在这样的主应用程序线程中使用ClientData类-
    Mappings mappings = ClientData.getMappings(); 
    // use mappings.primary 
    // use mappings.secondary 
    // use mappings.tertiary
    

    此代码会对性能产生影响吗?简而言之,哪一种更好,我应该使用ReentrantReadWriteLock还是上述CountDownLatch一种解决方案?

    所以现在的问题是,CountDownLatch解决方案与ReentrantReadWriteLock解决方案?在高读/低写用例中,哪一个性能更好?

    如果可能的话,有人可以在我上面的解决方案中提供一个使用ReentrantReadWriteLock的示例吗?这样一来,我就可以将性能与CountDownLatch解决方案和ReentrantReadWriteLock解决方案的性能进行比较。

    由于我无法为上述用例提供一个使用ReentrantReadWriteLock的解决方案。

    注意:-

    就我而言,写入将每三个或四个月发生一次。但是读取会以每秒1000请求的非常高的速度从多个线程进行。因此它必须非常快。

    最佳答案

    您应该通过引入Mappings继续进行重组。您不需要单独管理这三个引用(这变得很复杂)。而是管理一个引用。

    class ClientData {
      // This is immutable after creation (like your OP)
      class Mappings { /* definition from your original post */ }
    
      // this should be volatile;
      private static volatile Mappings instance;
    
      // the read path proceeds without acquiring any lock at all.  Hard to 
      // get faster than a volatile read.  Note the double-checked locking pattern
      // works JDK 6 or greater when using volatile references (above)
      public static Mappings getMappings() {
        Mappings result = instance;
        if(result == null) {
           synchronized(ClientData.class) {
              result = instance;
              // recall while() is required to handle spurious wakeup
              while(result == null) {
                 ClientData.class.wait();
                 result = instance;
              }
           }
        }
      }
    
      public static setMappings(Map one, Map two, Map three) {
        synchronized(ClientData.class) {
          instance = new Mappings(one,two,three);
          ClientData.class.notifyAll()
        }
      }
    }
    

    我认为这样做有以下好处:
  • 读取路径上不需要锁。这是Mappings类的不变性的副作用。易读的速度非常快。
  • 在getMappings()之前输入的调用者只需等待它被设置。
  • 无需担心
  • 的第三方对象语义

    不幸的是(IMHO)Java没有内置良好的“Waitable Reference”。但是,人们不能要求一切!第三方库有一些支持-Guava的Suppliers.memoize()是一个不错的起点。

    祝您的项目好运。

    关于java - 如何在使用RentrantLock完成所有三个 map 的初始化之前避免读取,并在更新完成后返回更新的 map 集?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23369069/

    相关文章:

    c# - 如何复制具有取消复制功能的文件?

    java - 线程、对象状态

    java - setMaximumFractionDigits不适用于ThreadLocal <NumberFormat>

    java - 将扑克牌数组列表中的所有数值相加

    java - 仅将文本留在括号内

    java - 如何编辑从 xsd 生成的 java 类

    java - 如何创建一个共享计时器,在第一次初始化后减少

    wpf - 对于我的游戏的主循环,我应该考虑什么 : DispatcherTimer Vs StoryBoard

    java - 如何做包含JDBC和JMS事务的XA事务?

    java - StringBuilder 是线程安全的(与 parallelStream 一起使用)吗?