首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何避免在使用RentrantLock初始化所有三个映射之前进行读取,并在完成更新后返回更新的映射集?

如何避免在使用RentrantLock初始化所有三个映射之前进行读取,并在完成更新后返回更新的映射集?
EN

Stack Overflow用户
提问于 2014-04-29 15:33:18
回答 4查看 271关注 0票数 1

我试图实现锁,每当我对三个映射进行写入时,都要避免读取。所以我的要求是-

  1. 读取块,直到第一次设置所有三个映射为止。
  2. 现在,第二次,如果我正在更新地图,我仍然可以返回所有三个旧的映射值(在对所有三个映射完成更新之前),或者在对所有三个映射进行更新时,它应该阻止并返回所有新的三个映射值。

因为我有三个映射- primaryMappingsecondaryMappingtertiaryMapping,所以它应该返回三个更新映射的所有新值,或者应该返回映射的所有旧值。基本上,在更新时,我不希望返回具有旧值的primaryMapping、具有新值的secondaryMapping和具有新值的tertiaryMapping

它应该是一致的,要么应该返回旧值,要么应该在更新映射后返回新值。在我的例子中,地图的更新将每三个月或四个月进行一次。

下面是我的ClientData类,在这个类中,我使用的是整个逻辑所在的ReentrantLock -

代码语言:javascript
运行
复制
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类变量中。

代码语言:javascript
运行
复制
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);
        }
    }
}

我将在getPrimaryMappinggetSecondaryMappinggetTertiaryMapping类中从主应用程序的主线程中使用ClientData类,因此我希望返回这三个映射中的所有新值集,或者如果正在进行更新,那么要么阻止它,然后在更新完成后返回这三个映射的所有新值集。

问题陈述:-

在我的代码库(如上面的ClientData类中所示)中,我想,一旦第一次设置了映射,我就无法更新它,因为这一行将导致问题,它将引发异常,然后如何实现上面所示的第二点?

代码语言:javascript
运行
复制
// how  to avoid this?
if (allset) {
    throw new IllegalStateException("All the maps are already set");
}

我怎样才能成功地落实上述两点?我想,这里有件很小的东西我错过了吗?我想在这里使用ReentrantLock,但也欢迎任何其他建议。我主要关心的是性能问题。因为我会每三个月在这三张地图上做一次,所以这很好。但是,每每秒1000次请求,从主应用程序代码中得到三个映射,所以我想要非常快。

一开始,我想把这句话删除-

代码语言:javascript
运行
复制
// how  to avoid this?
if (allset) {
    throw new IllegalStateException("All the maps are already set");
}

但我怀疑,它不会工作,因为那时会有错误的映射之间的线程之间,而我正在更新地图?

这就是我从主应用程序线程读取来自ClientData class的值的方式-

代码语言:javascript
运行
复制
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类-

代码语言:javascript
运行
复制
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类,如-

代码语言:javascript
运行
复制
Mappings mappings = ClientData.getMappings(); 
// use mappings.primary 
// use mappings.secondary 
// use mappings.tertiary

此代码对性能有任何影响吗?总之,哪一个会更好,我应该使用ReentrantReadWriteLock还是上面的CountDownLatch一个解决方案?

那么现在的问题是,CountDownLatch解决方案还是ReentrantReadWriteLock解决方案?在高读/低写用例中,哪一个会表现得更好?

如果可能的话,有人还能提供一个使用ReentrantReadWriteLock基础的例子吗?这样,我将能够将性能与前面的CountDownLatch解决方案和ReentrantReadWriteLock解决方案进行比较。

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

注:-

在我的例子中,写作将在三四个月内发生一次。但是读取将以非常高的速度(每秒1000请求)从多个线程进行。所以它必须非常快。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2014-05-07 04:41:56

您应该按照引入Mappings的方式继续您的重组。您不需要单独管理这三个引用(这是它变得复杂的地方)。相反,管理一个引用。

代码语言:javascript
运行
复制
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()
    }
  }
}

我认为这有以下好处:

  1. 读取路径不需要锁。这是映射类不可变的副作用。易失读是非常快的。
  2. 在getMappings()之前输入它的调用者只需等待设置它。
  3. 没有第三方对象语义需要担心。

java没有内置一个好的“”,这有点不幸(IMHO)。但是一个人不能要求所有的东西!第三方库有一些支持

祝你的项目好运。

票数 2
EN

Stack Overflow用户

发布于 2014-04-29 22:14:53

您正确地认为需要删除if (allset)块。否则你将无法更新。

因此,看起来您必须解决可能发生的以下问题:

  1. 你叫ClientData.getPrimaryMapping()
  2. 出现了一个疯狂的更新。
  3. 你打电话给ClientData.getSecondaryMapping() -现在你有了不匹配的数据

我将创建一个单独的方法来同时获取所有映射,而不是在两者之间调用所有的映射方法。该方法应该返回映射的列表(或数组或其他类型的容器)。通过将锁移动到此方法,您可以确保在获得地图时不会有任何更新。

例如,类似这样的东西:

代码语言:javascript
运行
复制
// create a new list on each update
private static List<Map<String, Map<Integer, String>>> mappings;

public static final List<Map<String, Map<Integer, String>>> getAllMappings() {
    lock.lock();
    try {
        while (!allset) {
            allsetnow.await();
        }
        return mappings;
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // reset interruptedd state.
        throw new IllegalStateException(e);
    } finally {
        lock.unlock();
    }

}

我想它也可能更快,因为更少的方法调用和更少的尝试捕获块,但如果你真的关心性能,你应该衡量什么表现最好。

票数 1
EN

Stack Overflow用户

发布于 2014-04-30 10:12:39

如果您有这样的代码块

代码语言:javascript
运行
复制
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);

getMapping方法中,您无法修复可能的竞赛。data1data2data3的值不一致,无论在被调用的方法中同步得有多好(一旦允许更新),这三行之间总是可以进行更新。

解决这个问题的唯一方法是将同步逻辑移到调用方中。在上面的示例代码中,所有三条语句都必须放在同一个受保护的块中。好消息是,这将有助于您的性能,因为如果您正确地这样做,您就不需要在被调用的方法中锁定。因此,与其三次锁定和解锁,不如只对整个块执行一次。

对于大量读取和偶尔更新的用例而言,使用允许并发读取的ReadWriteLock是一种自然的选择。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23369069

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档