java并发:多个写操作者,一个读操作者

20 浏览
0 Comments

java并发:多个写操作者,一个读操作者

我需要在我的软件中收集一些统计数据,并且我试图使其快速和正确,这对我来说并不容易!\n首先是我目前的代码,有两个类,StatsService和StatsHarvester。\npublic class StatsService {\n private Map stats = new HashMap(1000);\n \n public void notify(String key) {\n Long value = 1l;\n synchronized (stats) {\n if (stats.containsKey(key)) {\n value = stats.get(key) + 1;\n }\n stats.put(key, value);\n }\n }\n \n public Map getStats() {\n Map copy;\n synchronized (stats) {\n copy = new HashMap(stats);\n stats.clear();\n }\n return copy;\n }\n}\n这是我的第二个类,一个收集统计数据并将其写入数据库的收割机。\npublic class StatsHarvester implements Runnable {\n private StatsService statsService;\n private Thread t;\n \n public void init() {\n t = new Thread(this);\n t.start();\n }\n \n public synchronized void run() {\n while (true) {\n try {\n wait(5 * 60 * 1000); // 5分钟\n collectAndSave();\n } catch (InterruptedException e) {\n e.printStackTrace();\n }\n }\n }\n \n private void collectAndSave() {\n Map stats = statsService.getStats();\n // 做一些操作,比如:\n // saveRecords(stats);\n }\n}\n运行时将有大约30个并发运行的线程,每个线程调用notify(key)大约100次。只有一个StatsHarvester调用statsService.getStats()。\n所以我有很多写入者和只有一个读取者。有准确的统计数据会很好,但是如果在高并发情况下丢失一些记录,我不在乎。\n读取器应该每5分钟运行一次或者其他合理的时间。\n写入应该尽可能快。读取应该快,但如果每5分钟锁定300毫秒,也是可以接受的。\n我已经阅读了很多文档(如《Java并发编程实战》、《Effective Java》等),但我强烈感觉我需要您的建议才能做到正确。\n我希望我陈述的问题足够明确简洁以获得有价值的帮助。\n


\n

编辑

\n感谢大家详细而有帮助的答案。正如我预料的那样,有不止一种方法可以解决这个问题。\n我测试了你们大部分提议(我理解的那些),并上传了一个测试项目到谷歌代码以供参考(maven项目)。\n我测试了不同实现的StatsService:\n

    \n

  • HashMapStatsService (HMSS)
  • \n

  • ConcurrentHashMapStatsService (CHMSS)
  • \n

  • LinkedQueueStatsService (LQSS)
  • \n

  • GoogleStatsService (GSS)
  • \n

  • ExecutorConcurrentHashMapStatsService (ECHMSS)
  • \n

  • ExecutorHashMapStatsService (EHMSS)
  • \n

\n并且我用每个调用notify y次的x个线程测试了它们,结果以毫秒为单位:\n

         10,100   10,1000  10,5000  50,100   50,1000  50,5000  100,100  100,1000 100,5000 
GSS       1        5        17       7        21       117      7        37       254       Summe: 466
ECHMSS    1        6        21       5        32       132      8        54       249       Summe: 508
HMSS      1        8        45       8        52       233      11       103      449       Summe: 910
EHMSS     1        5        24       7        31       113      8        67       235       Summe: 491
CHMSS     1        2        9        3        11       40       7        26       72        Summe: 171
LQSS      0        3        11       3        16       56       6        27       144       Summe: 266

\n目前我认为我将使用ConcurrentHashMap,因为它提供了良好的性能,同时也很容易理解。\n感谢大家的意见!\nJanning

0
0 Comments

在Java中,当有多个写入者和一个读取者时,可能会出现并发性问题。这种情况下,多个写入者同时写入数据,而读取者在读取数据时可能会读到不一致的结果。为了解决这个问题,可以使用Java的util.concurrent库。

首先,可以使用ConcurrentLinkedQueue来实现该解决方案。每个写入者可以自由地向这个队列中写入数据,而不需要担心其他写入者。它可以将一个带有统计数据的对象放入队列中。

接下来,读取者可以持续地从队列中取出数据并进行处理。然后,它可以按照自己的需求进行存储。

使用ConcurrentLinkedQueue可以确保多个写入者之间的并发写入没有问题,并且读取者可以按照自己的节奏读取数据进行处理。

下面是使用ConcurrentLinkedQueue的示例代码:

import java.util.concurrent.ConcurrentLinkedQueue;
public class Example {
    private static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>();
    public static void main(String[] args) {
        // 创建多个写入者线程
        for (int i = 0; i < 5; i++) {
            new Thread(new Writer()).start();
        }
        // 创建读取者线程
        new Thread(new Reader()).start();
    }
    static class Writer implements Runnable {
        @Override
        public void run() {
            // 写入数据到队列
            Object data = new Object();
            queue.offer(data);
        }
    }
    static class Reader implements Runnable {
        @Override
        public void run() {
            // 持续从队列中取出数据并进行处理
            while (true) {
                Object data = queue.poll();
                if (data != null) {
                    // 处理数据
                    process(data);
                }
            }
        }
        private void process(Object data) {
            // 处理数据的逻辑
        }
    }
}

使用ConcurrentLinkedQueue可以有效地解决多个写入者和一个读取者之间的并发性问题,保证数据的一致性和正确性。

0
0 Comments

Java并发:多个写者,一个读者

在并发编程中,一个常见的问题是多个写者和一个读者的情况。多个写者同时修改共享数据可能导致数据不一致的问题,而一个读者同时读取共享数据不会引发问题。为了解决这个问题,可以使用java.util.concurrent.ConcurrentHashMap来处理并发访问,避免对整个Map进行无用的锁定,并节省大量的工作。

ConcurrentHashMap是一个支持全面并发的哈希表,它支持可调整的更新操作的预期并发性。尽管所有操作都是线程安全的,但检索操作不需要锁定,并且没有任何支持可以阻止所有访问的锁定整个表的机制。可以通过指定并发级别来设置其并发级别,以指导更新操作之间的允许并发性。理想情况下,您应该选择一个值来容纳将同时修改表的线程数量。过高或过低的值都会导致问题,但是数量估计在一个数量级内的高估和低估通常不会有太大的影响。当只有一个线程修改且所有其他线程只读时,值为1是适当的。

要确保原子性,可以使用ConcurrentMap接口中的putIfAbsent、replace和remove等操作。然而,这种方法很容易导致丢失的更新。根据Map的契约,它不应该允许丢失的更新,可以假设putIfAbsent是原子执行的。为了确保这一点,需要使用循环直到replace操作返回true。另外,可以考虑将AtomicLong或AtomicInteger作为Map的值,这将消除丢失增量的问题。

对于多个写者和一个读者的情况,可以使用ConcurrentHashMap来解决并发访问的问题。通过选择适当的并发级别和使用原子操作,可以确保数据的一致性和原子性。同时,使用AtomicLong或AtomicInteger作为Map的值可以消除丢失增量的问题。

0
0 Comments

Java并发:多个写者,一个读者

在多个写者和一个读者的情况下,可能会出现并发问题,需要解决并发问题以确保线程安全。为了解决这个问题,可以使用java.util.concurrent库中的ConcurrentHashMap和AtomicLong。

这里的代码使用了ConcurrentHashMap和AtomicLong来解决并发问题。首先,通过stats.get(key)方法获取key对应的AtomicLong对象,如果返回值为null,说明该key在ConcurrentHashMap中不存在,需要使用putIfAbsent(key, new AtomicLong(1))方法将其放入ConcurrentHashMap中。如果返回值不为null,则直接调用value.incrementAndGet()方法对AtomicLong对象进行增加操作。

这种解决方法既快速又线程安全。通过使用ConcurrentHashMap和AtomicLong,可以避免并发问题的发生。

需要注意的是,原始的代码中使用了putIfAbsent方法进行判断,但这并不是ConcurrentHashMap的正确使用方法。每次调用putIfAbsent都会锁定与hashCode相关联的单个条目,从而降低了性能。正确的做法是,在初始的get方法成功后,不再进行putIfAbsent操作,避免不必要的锁定。

除了上述解决方法,还要注意第二个方法getStats存在的问题,即复制和清除操作不是原子性的。针对这个问题,可以采取其他的解决方法。

,通过使用ConcurrentHashMap和AtomicLong,可以解决多个写者和一个读者的并发问题。但需要注意正确使用ConcurrentHashMap的putIfAbsent方法,避免不必要的锁定。另外,对于存在的其他问题,需要采取其他解决方法。

0