Jak właściwie to zsynchronizować?

0

Witam. Mam obiekt, który zwraca obrazki w ten sposób, aby tworzyła się animacja. Klasa tego obiektu wygląda w ten sposób:

public class AnimatedObject {

    //Wszystkie "ramki"
    private Image[] images = new Image[4];
    //Aktualna "ramka"
    private int image = 0;

    public AnimatedObject() {
        /*
         * ZAŁADOWANIE
         * WSZYSTKICH
         * OBRAZKÓW
         */
        //Uruchomienie wątku
        new AnimationThread().start();
    }

    //Metoda która zwraca aktualny obrazek obiektu
    public Image getImage() {
        return images[image];
    }

    //Wątek odpowiedzialny za zmianę aktualnego obrazka
    private class AnimationThread extends Thread {

        @Override
        public void run() {
            try {
                while (true) {
                    if (++image == 4) {
                        image = 0;
                    }
                    Thread.sleep(100);
                }
            } catch (InterruptedException ex) {
            }
        }
    }
}

W programie działają dwa wątki. Jeden to ten, który zmienia obrazek animowanego obiektu. Drugi to wątek główny, który pobiera od obiektu jego obrazek i rysuje go na panelu. Po uruchomieniu program działa jakiś czas po czym wyrzuca mi wyjątek "Array Index Out Of Bounds Exception".
Powodem jest to, że wątek zmieniający obrazek zostaje wywłaszczony przez wątek główny w chwili gdy jest on po inkrementacji zmiennej image, ale przed sprawdzeniem czy nie jest ona równa 4. W ten sposób metoda getImage() zwraca element o indeksie 4 z tablicy o rozmiarze 4. Oczywiście nie ma takiego elementu. Aby temu zapobiec postanowiłem wykorzystać synchronizację. Dotychczas próbowałem na dwa sposoby:

  1. Blok synchonizowany
synchronized (this) {
    if (++image == 4) {
        image = 0;
    }
}
  1. Wywoływanie tego kodu z synchronizowanej metody
    private class AnimationThread extends Thread {

        @Override
        public void run() {
            try {
                while (true) {
                    nextFrame();
                    Thread.sleep(100);
                }
            } catch (InterruptedException ex) {
            }
        }

        private synchronized void nextFrame() {
            if (++image == 4) {
                image = 0;
            }
        }
    }

Obie metody niestety nie działają i co jakiś czas dostaję ten wyjątek. Czy źle uzywam sychronizacji czy jest inny powód? Proszę o pomoc.

1

Wydaje mi się, że może tu zachodzić sytuacja kiedy instrukcja ++i powiększa wartość i z 3 do 4 i w tym momencie wywołujesz metodę getImage, która próbuje wyjąć coś spod indeksu 4 co powoduje wyrzucenie wyjątku.
Spróbuj w pętli zamienić ++i na i+1 (a inkrementować tylko w else po tym zerującym if-ie).

0

Napisz, jak odnosisz się w tym drugim wątku do zmiennej image, bo może błąd jest w tamtym wątku. Nie wygląda to na błędne użycie inkrementacji.

1

Na pierwszy rzut oka wydaje się, że faktycznie masz racje co do inkrementacji -> wywłaszczeniu -> sprawdzenie warunku.
Jak na mój gust to problem polega nie na synchronizacji (jeśli masz tylko jeden wątek obsługujący tę inkrementację to jest tu zupełnie nieprzydatna), ale w samej zmiennej image. Zauważ co Ci daje synchronizacja:
Otóż zapewnia tylko, że synchronizowany fragment kodu będzie wykonywany przez jeden wątek. I tylko tyle. Nie zapewni wątkowi wykonującemu synchronizowany kod wyłączności na dostęp do zmiennej image, którą zadeklarowałeś gdzie indziej.
Tak więc synchronizując metodę nextFrame() zapewniłeś sobie jedynie, że inkrementacji zmiennej image dokona jednocześnie tylko jeden wątek. W każdej chwili może jednak wątek zostać wywłaszczony i wątek główny może odczytać wartość zmiennej image równej 4 tuż przed jej wyzerowaniem, co prawdopodobnie u Ciebie ma miejsce.

Podpowiedź: Rozwiązaniem jest zapewnienie wstrzymania pracy wątku odczytującego obrazek z tablicy do czasu spełnienia warunku "image<=3" :)

0

Chodzi o to, że wywołując metodę, która jest synchronized ustawiasz blokadę na obiekt, który posiada tą metodę. Aby synchronizacja działała poprawnie musisz w tym drugim wątku umieścić

synchronized(/* tu obiekt, w którym jest ta metodą */)  {
       // dalsza część kodu
}

Nic nie szkodzi, że jeden wątek obsługuje inktrementację. Synchronizując musisz zrobić tak, aby kilka wątków nie zmieniało jednej zmiennej w jednym czasie.

0

umieść jakiekolwiek odwołania do obiektu licznika jako synchronized, lub wyciągnij sobie osobną klasę np SynchronizedCounter:

 
SynchroCounter{
private int i;
public synchronized int get(){
return i;
}
public synchronized int incrementAndGet(){
if(++i == 4){
i = 0;
}
return i;
}
}

innym wariantem może być bez synchronized:

 
SynchroCounter{
private volatile int i;
public int get(){
return i;
}
public int incrementAndGet(){
int tempI = i;
if(++tempI == 4){
tempI = 0;
}
return i = tempI;
}
}

i zamiast:
private int image = 0;
będzie
privaet final SynchroCounter synchroCounter;

troche nie rozumiem jak te obrazki się zmieniają, ale co w sytacji kiedy jeden wątek zmieni licznik ale jeszcze nie zdąży wygenerować nowego obrazka przez co będziemy chcieli pobrać obrazek o idx=3 ale wygenerowane są tylko te idx=0,1,2?

0
moskitek napisał(a):

troche nie rozumiem jak te obrazki się zmieniają, ale co w sytacji kiedy jeden wątek zmieni licznik ale jeszcze nie zdąży wygenerować nowego obrazka przez co będziemy chcieli pobrać obrazek o idx=3 ale wygenerowane są tylko te idx=0,1,2?

Eee, to sie nigdy nie zdarzy. Zauważ, że gość ładuje obrazki (choć nie wiemy jak) zanim wystartuje wątek podbijający licznik, więc zakładamy, że tablica zawiera już wszxystkie 4 obrazki gdy wątek inkrementujący licznik zaczyna działać.

Co do samego problemu - nie ma co wielce kombinować. Michat34, zapoznaj się z metodami wait()/ notify()/ notifyAll() - one są rozwiązaniem Twojego problemu.

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