Bezpieczny dostęp do ArrayList z wielu wątków -synchronized()

0

Witam

Piszę program (na androida), który ma mieć następującą budowę: w serwisie działającym w tle zadeklarowałem tablicę

List<myDevice> devices = new ArrayList<myDevice>();

Elementy tej tablicy są dodawane, usuwane i modyfikowane z poziomu serwisu. Serwis działa w tle.

W aplikacji jest też Activity, które odpowiedzialne jest za wyświetlanie informacji o elementach tablicy, kod mniej więcej taki:

...
String nazwy[] = new String[myService.devices.size()];
for(int i = 0 ; i < myService.devices.size() ; i++)
{
	nazwy[i] = myService.devices.get(i).name;
}

Zdarza mi się, że w czasie przepisywania nazw serwis zmodyfikuje tablicę DEVICES metodą np. devices.remove(1); . Aplikacja się wysypuje, ponieważ w pętli próbuje dostać się do elementu tablicy, który nie istnieje - błąd

java.lang.ArrayIndexOutOfBoundExeption

Poszukałem trochę po necie i znalazłem rozwiązanie, które działa - czyli zastosowałem synchronized(). W serwisie kiedy coś dodaję lub usuwam wywołuję metodę

synchronized(devices)
{
	devices.remove(0);
};

a w aktywności

...
synchronized(myService.devices)
{
	String nazwy[] = new String[myService.devices.size()];
	for(int i = 0 ; i < myService.devices.size() ; i++)
	{
		nazwy[i] = myService.devices.get(i).name;
	}
};

Moje pytanie jest takie: czy robię to dobrze? W serwisie wywołuję metodę remowe() tablicy, a w akywnosci - metodę get(). Czy ktoś może mi wytłumaczyć dlaczego to działa poprawnie (tzn. nie wysypuje aplikacji)?

0

Działa poprawnie, gdyż dostęp do obiektu jest blokowany na czas wykonywania na nim operacji, działa to jak mutex w c.
Np masz dwa wątki, które jednocześnie chcą usunąć ostatni element tablicy, któremuś się uda to zrobić, ale następny niestety wyjedzie poza zakres tablicy (bo usunęliśmy element więc liczba indeksów się zmniejszyła) i dostaniesz java.lang.ArrayIndexOutOfBoundExeption.

Rozwiązanie podane przez Ciebie przejdzie, ale jest mało eleganckie. Tzn. zawsze musisz pamiętać o

synchronized(myService.devices) { ... } 
 

Ja na Twoim miejscu ustawiłbym modyfikator private dla devices, a w klasie MyService dodał metody operujące na tablicy devices w stylu (usuń, dodaj etc).

public synchronized void delete( int index ) { ... }
 
0

Ewentualnie, jeśli liczba modyfikacji jest mała, a liczba wyświetleń duża, to możesz rozważyć klasę CopyOnWriteArrayList. Ta klasa operuje na snapshotach, tzn jest podobna do ArrayListy, tylko że każda operacja modyfikująca tworzy nową tablicę pod spodem (w ArrayLiście nowa tablica jest tylko gdy stara jest za mała lub za duża) - wniosek taki, że modyfikacje są drogie. Iteratory są przypisane do tych snapshotów, a nie do aktualnego stanu COWArrayListy, tak więc można ich np używać w foreach bez synchronizacji - a synchronizacja to często odczuwalny narzut.

Gdybyś zdecydował się na COWArrayListę (a niekoniecznie sprawdziłaby się ona w twoim projekcie), to musiałbyś zamienić String nazwy[] na List<String> nazwy, gdyż pomiędzy wywołaniami size() a iterator() mogłaby nastąpić jakaś modyfikacja z innego wątku.

1 użytkowników online, w tym zalogowanych: 0, gości: 1