2016-06-07 3 views
2

У меня есть метод, который должен подавать карту из очереди, и это только делает это, если размер карты не превышает определенного числа. Это вызвало проблему параллелизма, поскольку размер, который я получаю из каждого потока, является несовместимым глобальным. Я копировал проблему этого кодомМножество потоков, определяющих размер и плотность карты.

import java.sql.Timestamp; 
import java.util.Date; 
import java.util.concurrent.ConcurrentHashMap; 

public class ConcurrenthashMapTest { 
private ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<Integer, Integer>(); 
private ThreadUx[] tArray = new ThreadUx[999]; 

public void parallelMapFilling() { 
for (int i = 0; i < 999; i++) { 
    tArray[i] = new ThreadUx(i); 
} 
for (int i = 0; i < 999; i++) { 
    tArray[i].start(); 
} 
} 

public class ThreadUx extends Thread { 
private int seq = 0; 

public ThreadUx(int i) { 
    seq = i; 
} 

@Override 
public void run() { 
    while (map.size() < 2) { 
    map.put(seq, seq); 
    System.out.println(Thread.currentThread().getName() + " || The size is: " + map.size() + " || " + new Timestamp(new Date().getTime())); 
    } 
} 
} 

public static void main(String[] args) { 
new ConcurrenthashMapTest().parallelMapFilling(); 
} 
} 

Обычно я должен иметь только одну строку вывода и размера, не превышающий 1, но у меня есть некоторые вещи, как этот

Thread-1 || The size is: 2 || 2016-06-07 18:32:55.157 
Thread-0 || The size is: 2 || 2016-06-07 18:32:55.157 

Я попытался маркировками всего запустить метод как синхронизированный, но это не сработало, только тогда, когда я сделал это

@Override 
    public void run() { 
     synchronized (map) { 
     if (map.size() < 1) { 
      map.put(seq, seq); 
      System.out.println(Thread.currentThread().getName() + " || The size is: " + map.size() + " || " + new Timestamp(new Date().getTime())); 
     } 
     } 
    } 

Он работал, почему только блок синхронизации рабочего и синхронизирующий метод? Также я не хочу использовать что-то столь же старое, как блок синхронизации, поскольку я работаю над Java EE-приложением, есть ли исполнитель задачи Spring или Java EE или аннотация, которые могут помочь?

+1

Я не понимаю, что старое с синхронизированным? –

+0

ConcurrentHashMap не может дать строго согласованный размер, потому что он действительно не знает размер. Нет ничего плохого в синхронизации, просто прекратите читать эти блоги :) –

ответ

3

От Java Concurrency in Practice:

Семантика методов ConcurrentHashMap, которые действуют на всей карте, такие как size и isEmpty, были немного ослаблены, чтобы отразить одновременный характер коллекции. Поскольку результат размера может быть устаревшим к моменту его вычисления, это действительно только оценка, поэтому размер позволяет возвращать аппроксимацию вместо точного подсчета. Хотя вначале это может показаться тревожным, на самом деле методы, подобные size и isEmpty, гораздо менее полезны в параллельных средах, потому что эти величины являются движущимися целями. Поэтому требования к этим операциям были ослаблены, чтобы обеспечить оптимизацию производительности для наиболее важных операций, прежде всего get, put, containsKey и remove.

Единственная возможность, предлагаемая объектами synchronizedMap, но не ConcurrentHashMap - это возможность блокировки карты для эксклюзивного доступа. С Hashtable и synchronizedMap получение блокировки карты предотвращает доступ к ней других потоков. Это может потребоваться в необычных случаях, таких как добавление нескольких отображений атомарно или повторное использование Карты несколько раз и необходимость видеть одни и те же элементы в одном порядке. В целом, однако, это разумный компромисс: ожидается, что одновременные коллекции изменят свое содержание непрерывно.

Решения:

  1. дизайн Refactor и не используют size метод с параллельным доступом.

  2. Чтобы использовать методы как size и isEmpty, вы можете использовать синхронизированную коллекцию Collections.synchronizedMap. Синхронизированные коллекции обеспечивают безопасность потоков, сериализуя весь доступ к состоянию коллекции. Стоимость этого подхода - плохой параллелизм; когда несколько потоков борются за блокировку коллекции, страдает пропускная способность. Также вам нужно будет синхронизировать блок, где он проверяет и помещает с экземпляром карты, потому что это сложное действие.

В-третьих. Используйте стороннюю реализацию или напишите свой собственный.

public class BoundConcurrentHashMap <K,V> { 
    private final Map<K, V> m; 
    private final Semaphore semaphore; 
    public BoundConcurrentHashMap(int size) { 
     m = new ConcurrentHashMap<K, V>(); 
     semaphore = new Semaphore(size); 
    } 

    public V get(V key) { 
     return m.get(key); 
    } 

    public boolean put(K key, V value) { 
     boolean hasSpace = semaphore.tryAcquire(); 
     if(hasSpace) { 
      m.put(key, value); 
     } 
     return hasSpace; 
    } 

    public void remove(Object key) { 
     m.remove(key); 
     semaphore.release(); 
    } 

    // approximation, do not trust this method 
    public int size(){ 
     return m.size(); 
    } 
} 

Класс BoundConcurrentHashMap является столь же эффективным, как ConcurrentHashMap и почти потокобезопасна. Поскольку удаление элемента и освобождение семафора в методе remove не являются одновременными, как и должно быть. Но в этом случае это терпимо. Метод size по-прежнему возвращает приблизительное значение, но метод put не позволит превысить размер карты.

3

Вы используете ConcurrentHashMap, и в соответствии с API Doc:

Имейте в виду, что результаты методов агрегатных состояния, включая размер, IsEmpty и containsValue, как правило, полезны только тогда, когда карта не подвергается параллельным обновлениям в других потоках. В противном случае результаты этих методов отражают переходные состояния, которые могут быть адекватными для мониторинга или оценки, но не для управления программой.

Это означает, что вы не можете получить точный результат, если вы явно не синхронизируете доступ к size().

Добавить synchronized в метод run не работает, потому что потоки не синхронизируются на одном и том же объекте блокировки - каждый получает блокировку на себя.

Синхронизация на самой карте определенно работает, но ИМХО это нехороший выбор, потому что тогда вы теряете преимущество в производительности, которое может предоставить ConcurrentHashMap.

В заключение вам необходимо пересмотреть дизайн.

 Смежные вопросы

  • Нет связанных вопросов^_^