Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

[JAVA] Synchronizowanie Licznika

Ostatnio zmodyfikowano 2016-05-10 22:44
Autor Wiadomość
Nitr0Skay
Temat założony przez niniejszego użytkownika
[JAVA] Synchronizowanie Licznika
» 2016-01-25 19:44:33
Witam. Stanąłem przed poważnym problemem, którego nie do końca rozumiem, a nie mam za dużo czasu, by to rozgryźć.
Oto mam trzy klasy Javy, pierwsza z nich - klasa licznika, jest klasą przechowującą pewną wartość, która będzie obrabiana:

C/C++
public class Licznik {
    public int counter;
   
    public Licznik( int counterVal ) {
        counter = counterVal;
    }
}

Druga klasa ma za zadanie inkrementować powyższy licznik, a więc obrabiać wartość w jej wnętrzu. Ta klasa będzie odpalana w dwóch różnych wątkach:

C/C++
public class Increment implements Runnable {
    Licznik counter;
   
    public Increment( Licznik counter ) {
        this.counter = counter;
    }
   
    public void run() {
        for( int i = 0; i < 10000; i++ )
             counter.counter++;
       
    }
}

No i ostatnia - klasa uruchomieniowa:

C/C++
public class Wspolbieznie {
   
   
    public static void main( String[] args ) throws InterruptedException {
        Licznik ml = new Licznik( 0 );
        Thread thread1 = new Thread( new Increment( ml ) );
        Thread thread2 = new Thread( new Increment( ml ) );
        thread1.start();
        thread2.start();
        Thread.sleep( 1000 );
        System.out.println( "Wartość licznika: " + ml.counter );
    }
   
}

Mankament tkwi w tym, że mogę grzebać tylko w klasie uruchomieniowej, więc to też próbowałem. Otóż końcowa wartość licznika powinna wynosić 20000, ale jako iż dostęp do niego mają dwa różne wątki w tym samym czasie, to czasami pobierana jest nieaktualna już jego wartość. Pierwsze co mi przyszło do głowy, to zsynchronizowanie Licznika w klasie uruchomieniowej, i tak na logikę:

C/C++
public class Wspolbieznie {
   
   
    public static void main( String[] args ) throws InterruptedException {
        Licznik ml = new Licznik( 0 );
        Thread thread1 = new Thread( new Increment( ml ) );
        Thread thread2 = new Thread( new Increment( ml ) );
       
        synchronized( ml ) {
            thread1.start();
            thread2.start();
        }
       
        Thread.sleep( 1000 );
        System.out.println( "Wartość licznika: " + ml.counter );
    }
   
}

Powinno to zadziałać i dać wynik równy 20000, ale nie daje. Próbowałem wiele innych rzeczy, na przykład jeszcze to mogłoby być:

C/C++
public class Wspolbieznie {
   
   
    public static void main( String[] args ) throws InterruptedException {
        Licznik ml = new Licznik( 0 );
       
        synchronized( ml ) {
            Thread thread1 = new Thread( new Increment( ml ) );
            Thread thread2 = new Thread( new Increment( ml ) );
            thread1.start();
            thread2.start();
            Thread.sleep( 1000 );
           
        }
        System.out.println( "Wartość licznika: " + ml.counter );
    }
   
}

Ale i to nic nie daje. Mimo zsynchronizowania Licznika, nadal wątki mają do niego stały, niesynchroniczny dostęp. Nie wiem zatem, jak to zsynchronizować. Ktoś mi odpowie, dlaczego to nie działa ??
P-144009
Monika90
» 2016-01-26 12:54:03
Jeżeli nie można zmienić klasy Licznik lub Increment, to moim zdaniem niewiele da się zrobić. Jedyne co mi przychodzi do głowy (a zaznaczam, że nie znam Javy), to coś takiego:
C/C++
class SynchroIncrement extends Increment {
    public SynchroIncrement( Licznik counter ) {
        super( counter );
    }
   
    public void run() {
        synchronized( super.counter ) { super.run(); }
    }
}

class Wspolbieznie {
    public static void main( String[] args ) throws InterruptedException {
        Licznik ml = new Licznik( 0 );
        Thread thread1 = new Thread( new SynchroIncrement( ml ) );
        Thread thread2 = new Thread( new SynchroIncrement( ml ) );
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println( "Wartość licznika: " + ml.counter );
    }
}
Ale tutaj najpierw wykona się jeden wątek a potem drugi, wiec co to za współbieżność?
P-144025
Nitr0Skay
Temat założony przez niniejszego użytkownika
» 2016-01-26 18:39:00
Niestety, mam zmodyfikować klasę uruchomieniową, więc Pani rozwiązanie (tym bardziej, że wątki wykonują się po kolei) nie specjalnie mnie zadowala. Ale... początkowo myślałem, że mogę grzebać jedynie przy metodzie uruchomieniowej, ale jest wyraźnie napisane, iż klasę (podejrzewam, że to ma znaczenie). Do tego wymienione są obie techniki synchronizacji (mechanizmy synchronizowania bloku kodu
oraz synchronizowania metody.) - podejrzewam, iż to jest tutaj istotne i że zgodnie z tą interpretacją da się jakoś wykonać to zadanie. No nic, nie pozostało mi nic innego, jak tylko kombinować. Wielkie dzięki, bez Pani nie doszedł bym do tych spostrzeżeń ;D
P-144040
1aam2am1
» 2016-01-27 13:11:09
nie znam się na javie ale czy prościej nie było by dołożyć dodatkową klasę zabezpieczającą licznik przed dostępem z wielu wątków jednocześnie, jeżeli nie możesz edytować klasy licznik?
P-144060
Nitr0Skay
Temat założony przez niniejszego użytkownika
» 2016-01-27 16:10:40
Sek w tym, ze moge edytowac tylko klase uruchomieniowa...
P-144064
jankowalski25
» 2016-01-27 22:39:37
Jeśli możesz dziedziczyć po obu klasach, to przez polimorfizm i rzutowanie w górę można całkowicie zmienić działanie programu bez zmiany kodu klas bazowych. A co do synchronizacji, to spróbuj użyć klasy AtomicInteger zamiast typu
int
.

//edit: Możesz też spróbować użyć semaforów.

//edit2: Zaraz, coś mi tu nie pasuje:

C/C++
thread1.start();
thread2.start();
Thread.sleep( 1000 );
System.out.println( "Wartość licznika: " + ml.counter );

Skąd masz gwarancję, że oba wątki skończą się wykonywać przed wypisaniem wartości? W wątku głównym musisz na nie poczekać! A co, jeśli będą się wykonywały dłużej, niż sekundę z powodu chwilowego obciążenia systemu? Być może klasa CountDownLatch będzie tutaj skuteczna.
P-144088
C-Objective
» 2016-03-10 09:28:21
Nie lepiej wykonać pierwszego wątku na innej liczbie, drugiego na innej i zsumować wyniki?
Najprostsze rozwiązania zawsze najskuteczniejsze.
P-145826
Matix8741
Troszkę się spóźniłem
» 2016-05-10 22:44:10
Jako, że temat nadal jest na forum to odpowiem, mimo, że koledze już raczej nie pomogę. Zacznę od tego, że nie ma POLIMORFIZMU w Javie. W tym języku można dziedziczyć tylko po jednej klasie, a brak wielodziedziczenia jest rozwiązane za pomącą interfejsów, które już mogą być dziedziczone w nieokreślony sposób to jest np. to:
implements Runnable
. Ale do rzeczy w zadaniu chodziło jedynie (jak się domyślam) o zastosowanie czegoś takiego
C/C++
synchronized( thread2 ) {
    thread2.start();
    thread2.wait();
    thread1.start();
}
 Normalnie byłby to błąd bo funkcja
wait()
 bez żadnego
notify()
 albo
notifyAll()
 powoduje zagnieżdzenie albo przynajmniej zatrzymanie danego wątku, ale w ty przypadku
thread2
 uruchamia się po zakończeniu funkcji
Run()
 w
thread1
 (ok w increment), gdyż napiszę kuriozalnie nie ma już na kogo czekać. Co do tego co napisał Pan na górze, najłatwiej byłoby napisać
System.out.println( 20000 );
 :D
P-148097
« 1 »
  Strona 1 z 1