2016-07-22 12 views
5

Jakie są możliwe problemy spowodowane dodaniem elementów do niezsynchronizowanego obiektu z wieloma wątkami jednocześnie?Jakie są możliwe problemy spowodowane jednoczesnym dodawaniem elementów do niezsynchronizowanego obiektu ArrayList przez wiele wątków?

Próbowałem uruchomić kilka eksperymentów ze statyczną tablicą ArrayList z wieloma wątkami, ale nie mogłem znaleźć wiele.

Tutaj spodziewam się wielu skutków ubocznych niezsynchronizowania ArrayList lub podobnych obiektów w środowisku wielowątkowym.

Każdy dobry przykład pokazujący efekty uboczne byłby znaczący. dzięki.

poniżej to mój mały eksperyment, który przebiegał płynnie bez żadnego wyjątku.

Zastanawiam się również, dlaczego nie rzucił żadnych ConcurrentModificationException?

import java.util.ArrayList; 
import java.util.List; 

public class Experiment { 
    static List<Integer> list = new ArrayList<Integer>(); 
    public static void main(String[] args) { 
     for (int i = 0; i < 10; i++) { 
      System.out.println("A " + i); 
      new Thread(new Worker(list, "" + i)).start(); 
     } 
    } 
} 

class Worker implements Runnable { 
    List<Integer> al; 
    String name; 

    public Worker(List<Integer> list, String name) { 
     this.al = list; 
     this.name = name; 
    } 

    @Override 
    public void run() { 
     while (true) { 
      int no = (int) (Math.random() * 10); 
      System.out.println("[thread " + name + "]Adding:" + no + "to Object id:" + System.identityHashCode(al)); 
      al.add(no); 
     } 
    } 
} 
+2

Ponieważ dokumentacja stwierdza: "Ten wyjątek może zostać zgłoszony metodami, które wykryły współbieżną modyfikację obiektu **, gdy taka modyfikacja nie jest dopuszczalna. ***" AFAIK, 'ArrayList' nie wymusza reguł, aby tego nie zabronić. To "Iterator" robi. Jeśli chcesz zobaczyć wyniki, spróbuj usunąć elementy z jednego z wątków, podczas gdy inni dodają i zobacz, czy wyniki są zgodne z oczekiwaniami. W tej chwili wszystkie wątki są po prostu dodawane do listy (i drukowane, gdy tylko się do niej dodadzą, co stanowi największą wadę tego testu), więc nie można oczekiwać, że konsola będzie wyświetlać dziwne wyniki. –

+1

Twoje wątki są w rzeczywistości nieco zsynchronizowane z powodu 'System.out.println' ... – assylias

+1

Re," Próbowałem przeprowadzić kilka eksperymentów ... ale nie mogłem znaleźć wiele. " To sprawia, że ​​błędy współbieżności są tak podstępne: czasami mogą być trudne do odtworzenia. Czasami program, który zezwala na wątki niezsynchronizowanego dostępu do współużytkowanych danych, może przetrwać miesiące testów, tylko po to, aby zawiesić się w witrynie klienta po jej zwolnieniu. (Nie pytaj mnie, skąd wiem!) –

Odpowiedz

0

Oto prosty przykład: Dodam 1000 elementów do listy od 10 wątkach. Na końcu spodziewałbyś się 10.000 przedmiotów, ale prawdopodobnie nie. Jeśli uruchomisz go kilka razy, za każdym razem uzyskasz inny wynik.

Jeśli chcesz uzyskać wyjątek ConcurrentModificationException, możesz dodać for (Integer i : list) { } po pętli, która tworzy zadania.

public static void main(String[] args) throws Exception { 
    ExecutorService executor = Executors.newFixedThreadPool(10); 
    List<Integer> list = new ArrayList<>(); 
    for (int i = 0; i < 10; i++) { 
    executor.submit(new ListAdder(list, 1000)); 
    } 
    executor.shutdown(); 
    executor.awaitTermination(1, TimeUnit.SECONDS); 

    System.out.println(list.size()); 
} 

private static class ListAdder implements Runnable { 
    private final List<Integer> list; 
    private final int iterations; 

    public ListAdder(List<Integer> list, int iterations) { 
    this.list = list; 
    this.iterations = iterations; 
    } 

    @Override 
    public void run() { 
    for (int i = 0; i < iterations; i++) { 
     list.add(0); 
    } 
    } 
} 
0

ConcurrentModificationException występuje tylko podczas modyfikowania listy, gdy ta sama lista jest powtórzyć przy użyciu Iterator. Tutaj po prostu dodajesz dane do listy z wielu wątków, które nie wygenerują wyjątku. Spróbuj użyć iteratora w niektórych miejscach, gdzie & zobaczysz wyjątek.

Znajdź poniżej przykład zmodyfikowany w celu wygenerowania wyjątku ConcurrentModificationException.

public class Experiment { 
    static List<Integer> list = new ArrayList<Integer>(); 
    public static void main(String[] args) { 
     for (int i = 0; i < 10; i++) { 
      System.out.println("A " + i); 
      new Thread(new Worker(list, "" + i)).start(); 
     } 
     Iterator<Integer> itr = list.iterator(); 
     while(itr.hasNext()) { 
      System.out.println("List data : " +itr.next()); 
     } 
    } 
} 
2

Zazwyczaj napotykasz problemy podczas zmiany rozmiaru listy, aby pomieścić więcej elementów. Spójrz na realizację ArrayList.add()

public boolean add(E e) { 
    ensureCapacityInternal(size + 1); // Increments modCount!! 
    elementData[size++] = e; 
    return true; 
} 

jeśli nie ma synchronizacji, wielkość tablicowej, która będzie zmieniać wywołaniu ensureCapacityInternal a rzeczywistym wkładka. Spowoduje to ostatecznie wygenerowanie ArrayIndexOutOfBoundsException.

Oto kod, który produkuje takie zachowanie

final ExecutorService exec = Executors.newFixedThreadPool(8); 
final List<Integer> list = new ArrayList<>(); 
for (int i = 0; i < 8; i++) { 
    exec.execute(() -> { 
     Random r = new Random(); 
     while (true) { 
      list.add(r.nextInt()); 
     } 
    }); 
} 
+0

Dzięki @noscreen prawie o tym zapomniałem. –

1

Dodając element do unsunchronized ArrayList używane przez wiele wątku, można uzyskać wartość null zamiast rzeczywistej wartości, jak pożądane.

Dzieje się tak z powodu następującego kodu klasy ArrayList.

public boolean add(E e) { 
     ensureCapacity(size + 1); // Increments modCount!! 
     elementData[size++] = e; 
     return true; 
    } 

klasa ArrayList najpierw sprawdzić swój aktualny potencjał i jeśli wymagają następnie zwiększyć swoją zdolność (domyślnie pojemność wynosi 10, a następny przyrost (10 * 3)/2) i umieścić domyślną wartość poziomu klasy w nowej przestrzeni.

Załóżmy, że używamy dwóch wątków i oba przychodzą w tym samym czasie, aby dodać jeden element i okazało się, że domyślna pojemność (10) została wypełniona, a czas na zwiększenie jej pojemności. W pierwszym wątku pojawia się i zwiększa rozmiar ArrayList z wartością domyślną za pomocą metody ensureCapacity (10+ (10 * 3/2)) i umieść jej element w następnym indeksie (rozmiar = 10 + 1 = 11), a teraz nowy rozmiar to 11. Teraz drugi wątek przychodzi i zwiększa rozmiar tego samego ArrayList z wartością domyślną za pomocą metody ensureCapacity (10+ (10 * 3/2)) i wstaw jej element do następnego indeksu (rozmiar = 11 + 1 = 12), a teraz nowy rozmiar to 12. W tym przypadku otrzymasz zero na indeks 10, który jest wartością domyślną.

Oto ten sam kod powyżej.

package com; 

import java.util.ArrayList; 
import java.util.List; 

public class Test implements Runnable { 

    static List<Integer> ls = new ArrayList<Integer>(); 

    public static void main(String[] args) throws InterruptedException { 
     Thread t1 = new Thread(new Test()); 
     Thread t2 = new Thread(new Test()); 

     t1.start(); 
     t2.start(); 
     t1.join(); 
     t2.join(); 
     System.out.println(ls.size()); 
     for (int i = 0; i < ls.size(); ++i) { 
      System.out.println(i + " " + ls.get(i)); 
     } 
    } 

    @Override 
    public void run() { 
     try { 
      for (int i = 0; i < 20; ++i) { 
       ls.add(i); 
       Thread.sleep(2); 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

wyjściowa:

39 
0 0 
1 0 
2 1 
3 1 
4 2 
5 2 
6 3 
7 3 
8 4 
9 4 
10 null 
11 5 
12 6 
13 6 
14 7 
15 7 
16 8 
17 9 
18 9 
19 10 
20 10 
21 11 
22 11 
23 12 
24 12 
25 13 
26 13 
27 14 
28 14 
29 15 
30 15 
31 16 
32 16 
33 17 
34 17 
35 18 
36 18 
37 19 
38 19 
  1. Po uruchomieniu dwa lub trzy razy dostaniesz wartość null kiedyś o indeksie 10 i od pewnego czasu w 16.

  2. Jak wspomniano w powyższym Odpowiedź przez noscreenname możesz uzyskać ArrayIndexOutOfBoundsException z tego kodu. Jeśli usuniesz Thread.sleep (2), będzie on generował się często.

  3. Proszę sprawdzić całkowity rozmiar tablicy, która jest mniejsza niż wymagana. Zgodnie z kodem powinien wynosić 40 (20 * 2), ale za każdym razem otrzymasz inny.

Uwaga: Możliwe, że konieczne będzie wielokrotne uruchomienie tego kodu w celu wygenerowania jednego lub wielu scenariuszy.

Powiązane problemy