简体   繁体   English

Java-HashMap值-易失性

[英]Java - HashMap values - volatile

I have a situation where I have N threads that do same the job, and one thread X that does something different. 我遇到的情况是,我有N个线程执行相同的工作,而一个线程X执行不同的操作。

Each thread N reads/writes into a static object which is of a class (called MMLCounter) which is wrapper around HashMap, and each thread works with different key/value pair of that HashMap, so all threads read/write values into the HashMap at the same time. 每个线程N都读取/写入一个静态对象,该对象属于HashMap的包装类(称为MMLCounter),并且每个线程都与该HashMap的不同键/值对一起工作,因此所有线程都将值读取/写入HashMap中。同一时间。 Thread X periodically needs to access all values, and while it's accessing them (from the moment it accesses first value till the moment it accesses the last value, none of the other N threads may change the values in HashMap). 线程X定期需要访问所有值,并且在访问它们时(从访问第一个值的那一刻到访问最后一个值的那一刻,其他N个线程都不能更改HashMap中的值)。

HashMap is initialized and key/values added to it by threads during thread creation at the start of program execution, and later no new key/values are added, only values in HashMap change. 初始化HashMap,并在程序执行开始时在线程创建期间由线程向其添加键/值,此后不添加新的键/值,仅HashMap中的值会更改。

Because of this I didn't use ConcurrentHashMap or synchronized functions, but instead I created a wrapper class around HashMap, which additionally has a flag which signals the N threads are they allowed to change the values, and this flag is changed exclusively by thread X. 因此,我没有使用ConcurrentHashMap或同步函数,而是创建了一个围绕HashMap的包装类,该包装类还具有一个标志,该标志指示N个线程允许它们更改值,并且该标志专门由线程X更改。 。

This way all N threads can work with HashMap in parallel, but when thread X starts its work only it can work with the HashMap until it finishes. 这样,所有N个线程都可以与HashMap并行工作,但是当线程X开始其工作时,它只能与HashMap一起工作,直到完成为止。

My question here is do I need to declare anything as volatile (for example values in HashMap), and if yes, what and how? 我的问题是,我是否需要将任何内容声明为volatile(例如HashMap中的值),如果是,则什么以及如何进行声明?

Thing what I would like to avoid (don't know if it is possible that it happens) is that one of the N threads changes a value in HashMap, but that change of value is only reflected in local cached memory of that thread, and when thread X reads that value from HashMap it will read it from its local cached memory which is not in sync with local cached memory of the other N thread, meaning it will have a old value. 我要避免的事情(不知道是否有可能发生)是N个线程之一更改了HashMap中的值,但是值的更改仅反映在该线程的本地缓存中,并且当线程X从HashMap读取该值时,它将从其本地缓存的内存中读取该值,该内存与其他N个线程的本地缓存的内存不同步,这意味着它将具有旧值。

Here is the code: 这是代码:

public static void main(String[] args) throws ProtocolException { 

    int NUMBER_OF_THREADS = 400; 

    List<Future<?>> futureList = new ArrayList<Future<?>>(); 
    ExecutorService executor = Executors.newFixedThreadPool(NUMBER_OF_THREADS+1);
    futureList.add(executor.submit(new Runnable() {
        @Override
        public void run() { 
                int measureInterval = 10000;
                try {
                    Thread.sleep(measureInterval);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } 
                System.out.println("--> MML rate is : " + MMLGenerator.MML_COUNTER.getMMLRate(measureInterval/1000) + " MML per second.");
        }
    }));

    //create and start sending threads. 
    for (int threadNmbr = 0; threadNmbr < NUMBER_OF_THREADS; threadNmbr++) {
        futureList.add(executor.submit(new Thread(new MMLGenerator(threadNmbr))));
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    } 

    //wait for threads to finish. 
    for (Future<?> future : futureList) { 
        try { 
            future.get(); 
        } catch (InterruptedException e) {
        } 
        catch (ExecutionException e) { 
            throw (RuntimeException) e.getCause(); 
        } 
    } 
    executor.shutdown();
} 

class MMLGenerator implements Runnable {

    public static volatile MMLCounter MML_COUNTER = new MMLCounter();
    private int threadNmbr = 0;

    public MMLGenerator(int threadNmbr) {
        this.threadNmbr = threadNmbr;
        MMLGenerator.MML_COUNTER.put(this.threadNmbr, 0);

    }

    @Override
    public void run() {
        while(RUN_ACTIVE) {
            MML_COUNTER.increaseCounter(this.threadNmbr);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

public class MMLCounter {

  private Map<Integer,Integer> MMLCounter = new HashMap<Integer, Integer>();
  private boolean MMLCounterLocked = false;

  public Integer get(Integer key) {
      return this.MMLCounter.get(key);
  }

  public Integer put(Integer key, Integer value) {
      while (this.MMLCounterLocked) {
          try {
              Thread.sleep(100);
          } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
      }
      return this.MMLCounter.put(key, value);
  }

  public void increaseCounter(Integer key) {
      while (this.MMLCounterLocked) {
          try {
              Thread.sleep(100);
          } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
      }
      this.MMLCounter.put(key,this.MMLCounter.get(key).intValue() + 1);
  }

  public int getMMLRate(int measurementTime) {
      this.MMLCounterLocked = true;
      int MMLCounterSum = 0;
      for (Integer counterID : this.MMLCounter.keySet()) {
          int counter = this.MMLCounter.get(counterID);
          MMLCounterSum += counter;
          this.MMLCounter.put(counterID, 0);
      }
      this.MMLCounterLocked = false;
      return MMLCounterSum/measurementTime;
  }

} }


AFTER MODIFICATION 修改后

Thank you everybody for help. 谢谢大家的帮助。 I just now read ReentrantReaderWriterLock description and that really is what I need. 我刚刚阅读了ReentrantReaderWriterLock描述,而这正是我所需要的。 Below is the modified code. 下面是修改后的代码。

However, I still have two questions: 但是,我仍然有两个问题:

1) Why do I need to use also ConcurrentHashMap instead of HashMap if I protected critical part of codes with ReentrantReaderWriterLock ? 1)如果我使用ReentrantReaderWriterLock保护代码的关键部分,为什么还需要使用ConcurrentHashMap代替HashMap

2) This usage of ReentrantReaderWriterLock will only substitute from my previous implementation the usage of flag which I now see was not done correctly. 2) ReentrantReaderWriterLock这种用法将仅从我以前的实现中替代我现在看到的标志使用不正确的用法。 However, I still have the problem of value objects in HashMap not being volatile, so different threads will each have their own locally cached copy of a value which is not in sync with locally cached copy of the value from other threads? 但是,我仍然存在HashMap的值对象不易变的问题,因此不同的线程将各自具有自己的本地缓存值副本,而该副本与其他线程的本地缓存副本值不同步吗?

public static void main(String[] args) throws ProtocolException { 

    int NUMBER_OF_THREADS = 400; 

    List<Future<?>> futureList = new ArrayList<Future<?>>(); 
    ExecutorService executor = Executors.newFixedThreadPool(NUMBER_OF_THREADS+1);
    futureList.add(executor.submit(new Runnable() {
        @Override
        public void run() { 
                int measureInterval = 10000;
                try {
                    Thread.sleep(measureInterval);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } 
                System.out.println("--> MML rate is : " + MMLGenerator.counter.getMMLRate(measureInterval/1000) + " MML per second.");
        }
    }));

    //create and start sending threads. 
    for (int threadNmbr = 0; threadNmbr < NUMBER_OF_THREADS; threadNmbr++) {
        futureList.add(executor.submit(new Thread(new MMLGenerator(threadNmbr))));
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    } 

    //wait for threads to finish. 
    for (Future<?> future : futureList) { 
        try { 
            future.get(); 
        } catch (InterruptedException e) {
        } 
        catch (ExecutionException e) { 
            throw (RuntimeException) e.getCause(); 
        } 
    } 
    executor.shutdown();
} 

class MMLGenerator implements Runnable {

    public static MMLCounter counter = new MMLCounter();
    private int threadNmbr = 0;

    public MMLGenerator(int threadNmbr) {
        this.threadNmbr = threadNmbr;
        MMLCounter.counter.put(this.threadNmbr, 0);

    }

    @Override
    public void run() {
        while(RUN_ACTIVE) {
            MMLCounter.counter.increaseCounter(this.threadNmbr);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

public class MMLCounter {

  private Map<Integer,Integer> counter = new HashMap<Integer, Integer>();
  public static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

  public Integer put(Integer key, Integer value) {
      lock.readLock().lock();
      Integer oldValue = this.counter.put(key, value);
      lock.readLock().unlock();
      return oldValue;
  }

  public void increaseCounter(Integer key) {
      lock.readLock().lock();
      this.counter.put(key,this.counter.get(key).intValue() + 1);
      lock.readLock().unlock();
  }

  public int getMMLRate(int measurementTime) {
      lock.writeLock().lock();
      int counterSum = 0;
      for (Integer counterID : this.counter.keySet()) {
          counterSum += this.counter.get(counterID);;
          this.counter.put(counterID, 0);
      }
      lock.writeLock().unlock();
      return counterSum/measurementTime;
  }
}

AFTER 2nd MODIFICATION 第二次修改后

I now figured out that logic I need to implement requires me to manipulate several counters, not just one, from multiple threads, and each thread at any time can change any counter. 现在,我发现我需要实现的逻辑要求我从多个线程操纵多个计数器,而不仅仅是一个计数器,并且每个线程可以随时更改任何计数器。 Below is my implementation, but I'm not certain if I in a good way in regards to performance and data consistency. 下面是我的实现,但是我不确定在性能和数据一致性方面是否很好。

I except to have random number of counters (number of counters will be know at start of execution) which will be identified by String value, and each Counter must count two values (first value will always increase, a second value only sometimes, but if they increase they need to increase at the same time). 除了具有随机数的计数器(在执行开始时将知道计数器的数量)之外,我将由String值标识,并且每个Counter必须计数两个值(第一个值将始终增加,第二个值有时仅会增加,但是如果他们增加了,他们需要同时增加)。 When I need to have sum of each Counter, I need fetch both Counter values in an atomic operation, and also from the time I fetch the first Counter till the time I fetch last Counter, none of the Counters may be changed by other threads. 当我需要每个计数器的总和时,我需要在原子操作中同时获取两个Counter值,以及从我获取第一个Counter到获取最后一个Counter的时间,所有其他计数器都不能更改任何Counter。

For demonstration purposes, as Counter identification (key in HashMap) I took String value of counter's ordinal number, and to determine which Counter needs to increased in each iteration of each thread, as well as determining if just one or both values of Counter need to increase, I used Random generator. 出于演示目的,作为Counter标识(HashMap中的键),我采用了counter的序数的String值,并确定每个线程的每次迭代中哪个Counter需要增加,以及确定是否只需要Counter的一个或两个值增加时,我使用了随机生成器。

public static void main(String[] args) { 

    int NUMBER_OF_THREADS = 400;


    MMLGenerator.counterNmbr(2);
    List<Future<?>> futureList = new ArrayList<Future<?>>(); 
    ExecutorService executor = Executors.newFixedThreadPool(NUMBER_OF_THREADS+1); 

    futureList.add(executor.submit(new Runnable() {
        @Override
        public void run() { 
            while(true)
            {
                int measureInterval = 10;
                try {
                    TimeUnit.SECONDS.sleep(measureInterval);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                MMLGenerator.lock.writeLock().lock();
                for (String counterId : MMLGenerator.counter.keySet()) {
                    MMLCounterSimple counter = MMLGenerator.counter.get(counterId).getCountAndReset();
                    System.out.println("--> For counter " + counterId + " total is : " + counter.getTotal() + ", and failed is : " + counter.getFailed());
                }
                MMLGenerator.lock.writeLock().unlock();
            }
        }
    }));

    //create and start sending threads. 
    for (int threadNmbr = 0; threadNmbr < NUMBER_OF_THREADS; threadNmbr++) {
        futureList.add(executor.submit(new Thread(new MMLGenerator())));
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    } 

    //wait for threads to finish. 
    for (Future<?> future : futureList) { 
        try { 
            future.get(); 
        } catch (InterruptedException e) {
        } 
        catch (ExecutionException e) { 
            throw (RuntimeException) e.getCause(); 
        } 
    } 
    executor.shutdown();
} 



class MMLGenerator implements Runnable {

    public static volatile HashMap<String, MMLCounter> counter = new HashMap<String, MMLCounter>();
    public static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);


    public static void counterNmbr(int counterNmbr) {
        lock.writeLock().lock();
        for(int i = 0; i < counterNmbr; i++) {
            counter.put(new Integer(i).toString(), new MMLCounter());
        }
        lock.writeLock().unlock();
    }

    @Override
    public void run() {
        while(RUN_PROVISIONING) {
            lock.readLock().lock();
            String counterID = new Integer(new Random().nextInt(counter.size())).toString();
            long failedInc = 0;
            if (new Random().nextInt(2) == 0) {
                failedInc = 1;
            }
            counter.get(counterID).increaseCounter(failedInc);
            lock.readLock().unlock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}


public class MMLCounter {

    private volatile long total = 0;
    private volatile long failed = 0;
    public static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);


    public synchronized void increaseCounter(long failedInc) {
        lock.writeLock().lock();
        total++;;
        failed = failed + failedInc;
        lock.writeLock().unlock();
    }

    public synchronized MMLCounterSimple getCountAndReset() {
        lock.writeLock().lock();
        MMLCounterSimple simpleCounter = new MMLCounterSimple(total, failed);
        total = 0;
        failed = 0;
        lock.writeLock().unlock();
        return simpleCounter;
    }

}


public class MMLCounterSimple {

    private long total = 0;
    private long failed = 0;

    public MMLCounterSimple(long total, long failed) {
        this.total = total;
        this.failed = failed;
    }

    public long getTotal() {
        return this.total;
    }

    public long getFailed() {
        return this.failed;
    }
}

As written, this isn't guaranteed to work as you expect. 如所写,这不能保证按预期工作。 There's no synchronization point between the writes performed by the N threads and the read performed by the X thread. N个线程执行的写入与X线程执行的读取之间没有同步点。 Even the MMLCounterLocked flag could be ignored by writers. MMLCounterLocked甚至可以忽略MMLCounterLocked标志。

In addition to making your code work correctly, using higher-level concurrency tools like ConcurrentMap will drastically simplify your code. 除了使代码正常工作外,使用更高级别的并发工具(例如ConcurrentMap还将极大地简化代码。


Since you only require the sum, a LongAccumulator will suffice, and it makes the code very simple and safe. 由于您只需要总和,因此LongAccumulator就足够了,它使代码非常简单和安全。

  public static void main(String[] args)
    throws Exception
  {
    int NUMBER_OF_THREADS = 400;
    ExecutorService executor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
    LongAccumulator sum = new LongAccumulator(Long::sum, 0);
    for (int i = 0; i < NUMBER_OF_THREADS; ++i) {
      executor.submit(new MMLGenerator(sum));
      TimeUnit.MILLISECONDS.sleep(50); /* Why??? */
    }
    int interval = 10;
    TimeUnit.SECONDS.sleep(interval);
    long rate = sum.getThenReset() / interval;
    System.out.println("--> MML rate is : " + rate + " MML per second.");
    executor.shutdownNow();
  }

  private static final class MMLGenerator
    implements Runnable
  {

    private final LongAccumulator counter;

    MMLGenerator(LongAccumulator counter)
    {
      this.counter = counter;
    }

    @Override
    public void run()
    {
      while (true) {
        counter.accumulate(1);
        try {
          TimeUnit.SECONDS.sleep(1);
        }
        catch (InterruptedException shutdown) {
          break;
        }
      }
    }

  }

As for your two new questions: 至于您的两个新问题:

  1. "Why do I need to use also ConcurrentHashMap instead of HashMap if I protected critical part of codes with ReentrantReaderWriterLock ?" “如果我使用ReentrantReaderWriterLock保护代码的关键部分,为什么还要使用ConcurrentHashMap而不是HashMap ?”

You didn't protect the critical part with a read-write lock. 没有使用读写锁来保护关键部分。 You are acquiring the read lock when you write the table, and the write lock when you read it. 当你的表, 锁,当你它你是获取读取锁。 The behavior of HashMap isn't defined under concurrent modification. HashMap的行为未在并发修改下定义。 You could cause threads that are using the table to hang. 您可能会导致正在使用该表的线程挂起。

Also, you should use a try-finally construct to ensure that you unlock regardless of any errors that occur. 另外,您应该使用try-finally结构来确保无论发生任何错误都可以解锁。

If you used a ConcurrentMap , threads could update without acquiring a lock on the entire table, which is what you'll be doing when you apply the read-write lock correctly. 如果您使用ConcurrentMap ,则线程可以更新而无需在整个表上获得锁,而正确应用读写锁时将执行此操作。

  1. "This usage of ReentrantReaderWriterLock will only substitute from my previous implementation the usage of flag which I now see was not done correctly. However, I still have the problem of value objects in HashMap not being volatile , so [will] different threads … each have their own locally cached copy of a value which is not in sync with locally cached copy of the value from other threads?" ReentrantReaderWriterLock这种用法将仅从我以前的实现中替代我现在看到的标志使用不正确的用法。但是,我仍然存在HashMap的值对象volatile ,因此[将]不同的线程……每个线程……他们自己的值的本地缓存副本与其他线程的值的本地缓存副本不同步?”

No, Lock acquisition does synchronize with other threads, and changes by other threads that happen before the lock is acquired will be visible. 不, Lock获取确实会与其他线程同步,并且其他线程在获取锁之前发生的更改将是可见的。 If you fixed your locking, the HashMap would work correctly, but it would do so by locking the entire table during updates. 如果修复了锁定,则HashMap将正常工作,但可以通过在更新过程中锁定整个表来实现。

Yes, you need volatile , without which there is no guarantee that other threads will see the change; 是的,您需要volatile ,否则就不能保证其他线程也会看到更改。 java may (and usually does) cache the value locally in another thread, so changing it locally won't make any difference on other threads waiting for it. Java可能(并且通常确实)将值本地缓存在另一个线程中,因此在本地更改它不会对其他等待它的线程产生任何影响。

For example, this line: 例如,这一行:

this.MMLCounterLocked = true;

is useless without the field being volatile . 在领域volatile情况下是无用的。


Why are you trying to implement your own locks? 为什么要尝试实现自己的锁? Just use java's locking by synchronizing on a static field or a static method. 只需通过同步静态字段或静态方法来使用Java的锁定。

You probably want to utilize a locking scheme that allows multiple readers and exclusive access for one writer, such as java.util.concurrent.locks.ReentrantReadWriteLock , rather than try to bake your own synchronization mechanism. 您可能想利用一种锁定方案,该方案允许多个读取者和一个写入者的独占访问,例如java.util.concurrent.locks.ReentrantReadWriteLock ,而不是尝试烘焙您自己的同步机制。

The docs say 医生说

A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing. ReadWriteLock维护一对关联的锁,一个用于只读操作,一个用于写入。 The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. 只要没有写程序,读锁就可以同时由多个读程序线程保持。 The write lock is exclusive. 写锁是排他的。

Combining what rob and erickson have said: 结合rob和erickson所说的话:

Use a ConcurrentHashMap this is thread-safe. 使用ConcurrentHashMap这是线程安全的。

Use a ReentrantReaderWriterLock , let all of the threads that are making edits to the map grab the ReadLock so they can edit all at the same time. 使用ReentrantReaderWriterLock ,让对地图进行编辑的所有线程都抓住ReadLock以便它们可以同时进行全部编辑。 The read side of the lock is a shared lock. 锁的读取端是共享锁。 Then the thread that needs a consistent view of the entire map should use the WriteLock to gain exclusive access to the map. 然后,需要对整个映射具有一致视图的线程应使用WriteLock获得对该映射的独占访问。

The reason that you need ConcurrentHashMap is so that all edits to the map including new entries will be seen by all threads. 之所以需要ConcurrentHashMap是因为所有线程都可以看到对地图的所有编辑,包括新条目。 Using the read side of the ReentrantReaderWriterLock here is not sufficient. 仅使用ReentrantReaderWriterLock的读取侧是不够的。

The reason that you should use the ReentrantReaderWriterLock is so when thread X needs exclusive access to the map it can have it, by using the writer side of the lock. 您应该使用ReentrantReaderWriterLock的原因是,当线程X需要对映射的独占访问时,可以通过使用锁的writer端来拥有它。

The big problem here is in increaseCounter there is a dependency that is not guaranteed to be atomic. 这里最大的问题是在increaseCounter存在一个不能保证是原子的依赖关系。 You get the value then you increment it. 您得到的值,然后增加它。 What happens when two threads call this method and both get the same value, then increase it. 当两个线程调用此方法并且都获得相同的值,然后增加该值时会发生什么。 If this is a bank account and someone lost a penny over the race-condition, well.... you get the idea. 如果这是一个银行帐户,并且有人因种族问题而损失了1分钱,那么.... There is an invariant here that is not upheld and needs some more fine-grain atomicity. 这里有一个不变性,它不成立,需要一些更细粒度的原子性。 Consider using an AtomicInteger as the value side, or check out all of the great classes in the Atomic package that offers single variables with atomic actions. 考虑将AtomicInteger用作值方,或者检查Atomic包中所有提供原子操作单个变量的出色类。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM